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;
|
||||
}
|
||||
|
||||
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[]) {
|
||||
return results.map((result) => {
|
||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||
|
||||
@ -1393,6 +1393,9 @@
|
||||
"BotSuggestedStatus" = "Do you want to set this emoji status suggested by **{bot}**?";
|
||||
"BotSuggestedStatusTitle" = "Set Emoji Status";
|
||||
"BotSuggestedStatusUpdated" = "Your emoji status is updated.";
|
||||
"BotDownloadFileTitle" = "Download File";
|
||||
"BotDownloadFileDescription" = "**{bot}** suggests you to download **{filename}**";
|
||||
"BotDownloadFileButton" = "Download";
|
||||
"PrivacyGifts" = "Gifts";
|
||||
"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."
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import download from '../../../util/download';
|
||||
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
@ -27,6 +28,7 @@ import renderText from '../../common/helpers/renderText';
|
||||
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||
@ -133,6 +135,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
|
||||
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
||||
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
||||
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string } | undefined>();
|
||||
const [bottomBarColor, setBottomBarColor] = useState<string | undefined>();
|
||||
const {
|
||||
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
||||
@ -190,7 +193,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
const isOpen = modal?.isModalOpen || false;
|
||||
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(() => {
|
||||
sendEvent({
|
||||
eventType: 'write_access_requested',
|
||||
@ -404,6 +422,41 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
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) {
|
||||
const result = await callApi('invokeWebViewCustomMethod', {
|
||||
bot: bot!,
|
||||
@ -576,6 +629,14 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
const { method, params, req_id: requestId } = eventData;
|
||||
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);
|
||||
@ -739,7 +800,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
isBackButtonVisible && styles.stateBack,
|
||||
);
|
||||
const backButtonCaption = shouldShowAppNameInFullscreen ? activeWebAppName
|
||||
: lang(isBackButtonVisible ? 'Back' : 'Close');
|
||||
: oldLang(isBackButtonVisible ? 'Back' : 'Close');
|
||||
|
||||
const hasHeaderElement = headerButtonCaptionRef?.current;
|
||||
|
||||
@ -895,22 +956,6 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
</Button>
|
||||
</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 && (
|
||||
<Modal
|
||||
isOpen={Boolean(popupParameters)}
|
||||
@ -933,27 +978,58 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleAppPopupClose(button.id)}
|
||||
>
|
||||
{button.text || lang(DEFAULT_BUTTON_TEXT[button.type])}
|
||||
{button.text || oldLang(DEFAULT_BUTTON_TEXT[button.type])}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</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
|
||||
isOpen={isCloseModalOpen}
|
||||
onClose={handleHideCloseModal}
|
||||
title={lang('lng_bot_close_warning_title')}
|
||||
text={lang('lng_bot_close_warning')}
|
||||
title={oldLang('lng_bot_close_warning_title')}
|
||||
text={oldLang('lng_bot_close_warning')}
|
||||
confirmHandler={handleConfirmCloseModal}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('lng_bot_close_warning_sure')}
|
||||
confirmLabel={oldLang('lng_bot_close_warning_sure')}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOpen={isRemoveModalOpen}
|
||||
onClose={handleHideRemoveModal}
|
||||
title={lang('BotRemoveFromMenuTitle')}
|
||||
textParts={renderText(lang('BotRemoveFromMenu', bot?.firstName), ['simple_markdown'])}
|
||||
title={oldLang('BotRemoveFromMenuTitle')}
|
||||
textParts={renderText(oldLang('BotRemoveFromMenu', bot?.firstName), ['simple_markdown'])}
|
||||
confirmHandler={handleRemoveAttachBot}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
|
||||
@ -1661,6 +1661,7 @@ bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||
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.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||
|
||||
@ -276,6 +276,7 @@
|
||||
"bots.getPopularAppBots",
|
||||
"bots.setBotInfo",
|
||||
"bots.getPreviewMedias",
|
||||
"bots.checkDownloadFileParams",
|
||||
"payments.getPaymentForm",
|
||||
"payments.getPaymentReceipt",
|
||||
"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;
|
||||
'BotSuggestedStatusTitle': undefined;
|
||||
'BotSuggestedStatusUpdated': undefined;
|
||||
'BotDownloadFileTitle': undefined;
|
||||
'BotDownloadFileButton': undefined;
|
||||
'PrivacyGifts': undefined;
|
||||
'PrivacyGiftsTitle': undefined;
|
||||
'PrivacyGiftsInfo': undefined;
|
||||
@ -1563,6 +1565,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'BotSuggestedStatus': {
|
||||
'bot': V;
|
||||
};
|
||||
'BotDownloadFileDescription': {
|
||||
'bot': V;
|
||||
'filename': V;
|
||||
};
|
||||
'StarsSubscribeBotButtonMonth': {
|
||||
'amount': V;
|
||||
};
|
||||
|
||||
@ -106,6 +106,10 @@ export type WebAppInboundEvent =
|
||||
custom_emoji_id: string;
|
||||
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'
|
||||
| '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'
|
||||
@ -194,6 +198,9 @@ export type WebAppOutboundEvent =
|
||||
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
||||
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
||||
}> |
|
||||
WebAppEvent<'file_download_requested', {
|
||||
status: 'cancelled' | 'downloading';
|
||||
}> |
|
||||
WebAppEvent<'main_button_pressed' |
|
||||
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
||||
| 'reload_iframe' | 'emoji_status_set', null>;
|
||||
|
||||
@ -39,6 +39,8 @@ async function processQueue() {
|
||||
function downloadOne({ url, filename }: PendingDownload) {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
link.download = filename;
|
||||
try {
|
||||
link.click();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user