| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "inline_bots/bot_attach_web_view.h"
- #include "api/api_blocked_peers.h"
- #include "api/api_common.h"
- #include "api/api_sending.h"
- #include "apiwrap.h"
- #include "base/call_delayed.h"
- #include "base/base_file_utilities.h"
- #include "base/qthelp_url.h"
- #include "base/random.h"
- #include "base/timer_rpl.h"
- #include "base/unixtime.h"
- #include "boxes/peer_list_controllers.h"
- #include "boxes/premium_preview_box.h"
- #include "boxes/share_box.h"
- #include "chat_helpers/stickers_lottie.h"
- #include "chat_helpers/tabbed_panel.h"
- #include "core/application.h"
- #include "core/click_handler_types.h"
- #include "core/local_url_handlers.h"
- #include "core/shortcuts.h"
- #include "core/ui_integration.h" // TextContext
- #include "data/components/location_pickers.h"
- #include "data/data_bot_app.h"
- #include "data/data_changes.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_emoji_statuses.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer_bot_command.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "data/data_web_page.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "data/stickers/data_stickers.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h"
- #include "info/bot/starref/info_bot_starref_common.h" // MakePeerBubbleButton
- #include "info/profile/info_profile_values.h"
- #include "inline_bots/inline_bot_result.h"
- #include "inline_bots/inline_bot_confirm_prepared.h"
- #include "inline_bots/inline_bot_downloads.h"
- #include "iv/iv_instance.h"
- #include "lang/lang_keys.h"
- #include "main/main_app_config.h"
- #include "main/main_domain.h"
- #include "main/main_session.h"
- #include "mainwidget.h"
- #include "payments/payments_checkout_process.h"
- #include "payments/payments_non_panel_process.h"
- #include "settings/settings_premium.h"
- #include "storage/storage_account.h"
- #include "storage/storage_domain.h"
- #include "ui/basic_click_handlers.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/chat/attach/attach_bot_webview.h"
- #include "ui/controls/location_picker.h"
- #include "ui/controls/userpic_button.h"
- #include "ui/effects/ripple_animation.h"
- #include "ui/painter.h"
- #include "ui/text/text_utilities.h"
- #include "ui/toast/toast.h"
- #include "ui/vertical_list.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/widgets/dropdown_menu.h"
- #include "ui/widgets/label_with_custom_emoji.h"
- #include "ui/widgets/menu/menu_item_base.h"
- #include "ui/widgets/popup_menu.h"
- #include "webview/webview_interface.h"
- #include "window/themes/window_theme.h"
- #include "window/window_controller.h"
- #include "window/window_peer_menu.h"
- #include "window/window_session_controller.h"
- #include "styles/style_boxes.h"
- #include "styles/style_channel_earn.h"
- #include "styles/style_chat.h"
- #include "styles/style_info.h" // infoVerifiedCheck.
- #include "styles/style_layers.h"
- #include "styles/style_menu_icons.h"
- #include "styles/style_window.h"
- #include <QSvgRenderer>
- namespace InlineBots {
- namespace {
- constexpr auto kProlongTimeout = 60 * crl::time(1000);
- constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000);
- constexpr auto kPopularAppBotsLimit = 100;
- [[nodiscard]] DocumentData *ResolveIcon(
- not_null<Main::Session*> session,
- const MTPDattachMenuBot &data) {
- for (const auto &icon : data.vicons().v) {
- const auto document = icon.match([&](
- const MTPDattachMenuBotIcon &data
- ) -> DocumentData* {
- if (data.vname().v == "default_static") {
- return session->data().processDocument(data.vicon()).get();
- }
- return nullptr;
- });
- if (document) {
- return document;
- }
- }
- return nullptr;
- }
- [[nodiscard]] PeerTypes ResolvePeerTypes(
- const QVector<MTPAttachMenuPeerType> &types) {
- auto result = PeerTypes();
- for (const auto &type : types) {
- result |= type.match([&](const MTPDattachMenuPeerTypeSameBotPM &) {
- return PeerType::SameBot;
- }, [&](const MTPDattachMenuPeerTypeBotPM &) {
- return PeerType::Bot;
- }, [&](const MTPDattachMenuPeerTypePM &) {
- return PeerType::User;
- }, [&](const MTPDattachMenuPeerTypeChat &) {
- return PeerType::Group;
- }, [&](const MTPDattachMenuPeerTypeBroadcast &) {
- return PeerType::Broadcast;
- });
- }
- return result;
- }
- [[nodiscard]] std::optional<AttachWebViewBot> ParseAttachBot(
- not_null<Main::Session*> session,
- const MTPAttachMenuBot &bot) {
- auto result = bot.match([&](const MTPDattachMenuBot &data) {
- const auto user = session->data().userLoaded(UserId(data.vbot_id()));
- const auto good = user
- && user->isBot()
- && user->botInfo->supportsAttachMenu;
- return good
- ? AttachWebViewBot{
- .user = user,
- .icon = ResolveIcon(session, data),
- .name = qs(data.vshort_name()),
- .types = (data.vpeer_types()
- ? ResolvePeerTypes(data.vpeer_types()->v)
- : PeerTypes()),
- .inactive = data.is_inactive(),
- .inMainMenu = data.is_show_in_side_menu(),
- .inAttachMenu = data.is_show_in_attach_menu(),
- .disclaimerRequired = data.is_side_menu_disclaimer_needed(),
- .requestWriteAccess = data.is_request_write_access(),
- } : std::optional<AttachWebViewBot>();
- });
- if (result && result->icon) {
- result->icon->forceToCache(true);
- }
- if (const auto icon = result->icon) {
- result->media = icon->createMediaView();
- icon->save(Data::FileOrigin(), {});
- }
- return result;
- }
- [[nodiscard]] PeerTypes PeerTypesFromNames(
- const std::vector<QString> &names) {
- auto result = PeerTypes();
- for (const auto &name : names) {
- //, bots, groups, channels
- result |= (name == u"users"_q)
- ? PeerType::User
- : name == u"bots"_q
- ? PeerType::Bot
- : name == u"groups"_q
- ? PeerType::Group
- : name == u"channels"_q
- ? PeerType::Broadcast
- : PeerType(0);
- }
- return result;
- }
- [[nodiscard]] Ui::LocationPickerConfig ResolveMapsConfig(
- not_null<Main::Session*> session) {
- const auto &appConfig = session->appConfig();
- auto map = appConfig.get<base::flat_map<QString, QString>>(
- u"tdesktop_config_map"_q,
- base::flat_map<QString, QString>());
- return {
- .mapsToken = map[u"maps"_q],
- .geoToken = map[u"geo"_q],
- };
- }
- [[nodiscard]] Window::SessionController *WindowForThread(
- base::weak_ptr<Window::SessionController> weak,
- not_null<Data::Thread*> thread) {
- if (const auto separate = Core::App().separateWindowFor(thread)) {
- return separate->sessionController();
- }
- const auto strong = weak.get();
- if (strong && strong->windowId().hasChatsList()) {
- strong->showThread(thread);
- return strong;
- }
- const auto window = Core::App().ensureSeparateWindowFor(thread);
- return window ? window->sessionController() : nullptr;
- }
- void ShowChooseBox(
- std::shared_ptr<Ui::Show> show,
- not_null<Main::Session*> session,
- PeerTypes types,
- Fn<void(not_null<Data::Thread*>)> callback,
- rpl::producer<QString> titleOverride = nullptr) {
- const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
- auto done = [=](not_null<Data::Thread*> thread) mutable {
- if (const auto strong = *weak) {
- strong->closeBox();
- }
- callback(thread);
- };
- auto filter = [=](not_null<Data::Thread*> thread) -> bool {
- const auto peer = thread->peer();
- if (!Data::CanSend(thread, ChatRestriction::SendInline, false)) {
- return false;
- } else if (const auto user = peer->asUser()) {
- if (user->isBot()) {
- return (types & PeerType::Bot);
- } else {
- return (types & PeerType::User);
- }
- } else if (peer->isBroadcast()) {
- return (types & PeerType::Broadcast);
- } else {
- return (types & PeerType::Group);
- }
- };
- auto initBox = [=](not_null<PeerListBox*> box) {
- if (titleOverride) {
- box->setTitle(std::move(titleOverride));
- }
- box->addButton(tr::lng_cancel(), [box] {
- box->closeBox();
- });
- };
- *weak = show->show(Box<PeerListBox>(
- std::make_unique<ChooseRecipientBoxController>(
- session,
- std::move(done),
- std::move(filter)),
- std::move(initBox)));
- }
- void ShowChooseBox(
- not_null<Window::SessionController*> controller,
- PeerTypes types,
- Fn<void(not_null<Data::Thread*>)> callback,
- rpl::producer<QString> titleOverride = nullptr) {
- ShowChooseBox(
- controller->uiShow(),
- &controller->session(),
- types,
- std::move(callback),
- std::move(titleOverride));
- }
- void FillDisclaimerBox(
- not_null<Ui::GenericBox*> box,
- Fn<void(bool accepted)> done) {
- const auto updateCheck = std::make_shared<Fn<void()>>();
- const auto validateCheck = std::make_shared<Fn<bool()>>();
- const auto callback = [=](Fn<void()> close) {
- if (validateCheck && (*validateCheck)()) {
- done(true);
- close();
- }
- };
- const auto padding = st::boxRowPadding;
- Ui::ConfirmBox(box, {
- .text = tr::lng_mini_apps_disclaimer_text(
- tr::now,
- Ui::Text::RichLangValue),
- .confirmed = callback,
- .cancelled = [=](Fn<void()> close) { done(false); close(); },
- .confirmText = tr::lng_box_ok(),
- .labelPadding = QMargins(padding.left(), 0, padding.right(), 0),
- .title = tr::lng_mini_apps_disclaimer_title(),
- });
- auto checkView = std::make_unique<Ui::CheckView>(
- st::defaultCheck,
- false,
- [=] { if (*updateCheck) { (*updateCheck)(); } });
- const auto check = checkView.get();
- const auto row = box->addRow(
- object_ptr<Ui::Checkbox>(
- box.get(),
- tr::lng_mini_apps_disclaimer_button(
- lt_link,
- rpl::single(Ui::Text::Link(
- tr::lng_mini_apps_disclaimer_link(tr::now),
- tr::lng_mini_apps_tos_url(tr::now))),
- Ui::Text::WithEntities),
- st::urlAuthCheckbox,
- std::move(checkView)),
- {
- st::boxRowPadding.left(),
- st::boxRowPadding.left(),
- st::boxRowPadding.right(),
- 0,
- });
- row->setAllowTextLines();
- row->setClickHandlerFilter([=](
- const ClickHandlerPtr &link,
- Qt::MouseButton button) {
- ActivateClickHandler(row, link, ClickContext{
- .button = button,
- .other = QVariant::fromValue(ClickHandlerContext{
- .show = box->uiShow(),
- })
- });
- return false;
- });
- (*updateCheck) = [=] { row->update(); };
- const auto showError = Ui::CheckView::PrepareNonToggledError(
- check,
- box->lifetime());
- (*validateCheck) = [=] {
- if (check->checked()) {
- return true;
- }
- showError();
- return false;
- };
- }
- WebViewContext ResolveContext(
- not_null<UserData*> bot,
- WebViewContext context) {
- if (!context.dialogsEntryState.key) {
- if (const auto strong = context.controller.get()) {
- context.dialogsEntryState = strong->dialogsEntryStateCurrent();
- }
- }
- if (!context.action) {
- const auto &state = context.dialogsEntryState;
- if (const auto thread = state.key.thread()) {
- context.action = Api::SendAction(thread);
- context.action->replyTo = state.currentReplyTo;
- } else {
- context.action = Api::SendAction(bot->owner().history(bot));
- }
- }
- if (!context.dialogsEntryState.key) {
- using namespace Dialogs;
- using Section = EntryState::Section;
- const auto history = context.action->history;
- const auto topicId = context.action->replyTo.topicRootId;
- const auto topic = history->peer->forumTopicFor(topicId);
- context.dialogsEntryState = EntryState{
- .key = (topic ? Key{ topic } : Key{ history }),
- .section = (topic ? Section::Replies : Section::History),
- .currentReplyTo = context.action->replyTo,
- };
- }
- return context;
- }
- void FillBotUsepic(
- not_null<Ui::GenericBox*> box,
- not_null<PeerData*> bot,
- base::weak_ptr<Window::SessionController> weak) {
- auto aboutLabel = Ui::CreateLabelWithCustomEmoji(
- box->verticalLayout(),
- tr::lng_allow_bot_webview_details(
- lt_emoji,
- rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
- Ui::Text::RichLangValue
- ) | rpl::map([](TextWithEntities text) {
- return Ui::Text::Link(std::move(text), u"internal:"_q);
- }),
- Core::TextContext({ .session = &bot->session() }),
- st::defaultFlatLabel);
- const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
- box->verticalLayout(),
- bot,
- st::infoPersonalChannelUserpic);
- Ui::AddSkip(box->verticalLayout());
- aboutLabel->setClickHandlerFilter([=](auto &&...) {
- if (const auto strong = weak.get()) {
- strong->showPeerHistory(
- bot->id,
- Window::SectionShow::Way::Forward);
- return true;
- }
- return false;
- });
- const auto title = Ui::CreateChild<Ui::RpWidget>(box->verticalLayout());
- const auto titleLabel = Ui::CreateChild<Ui::FlatLabel>(
- title,
- rpl::single(bot->name()),
- box->getDelegate()->style().title);
- const auto icon = bot->isVerified() ? &st::infoVerifiedCheck : nullptr;
- title->resize(
- titleLabel->width() + (icon ? icon->width() : 0),
- titleLabel->height());
- title->widthValue(
- ) | rpl::distinct_until_changed() | rpl::start_with_next([=](int w) {
- titleLabel->resizeToWidth(w
- - (icon ? icon->width() + st::lineWidth : 0));
- }, title->lifetime());
- if (icon) {
- title->paintRequest(
- ) | rpl::start_with_next([=] {
- auto p = Painter(title);
- p.fillRect(title->rect(), Qt::transparent);
- icon->paint(
- p,
- std::min(
- titleLabel->textMaxWidth() + st::lineWidth,
- title->width() - st::lineWidth - icon->width()),
- (title->height() - icon->height()) / 2,
- title->width());
- }, title->lifetime());
- }
- Ui::IconWithTitle(box->verticalLayout(), userpic, title, aboutLabel);
- }
- std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
- not_null<QWidget*> parent,
- not_null<PeerData*> peer,
- not_null<DocumentData*> document) {
- const auto emoji = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>(
- parent,
- object_ptr<Ui::FlatLabel>(
- parent,
- rpl::single(
- Ui::Text::SingleCustomEmoji(
- Data::SerializeCustomEmojiId(document->id),
- (document->sticker()
- ? document->sticker()->alt
- : QString()))),
- st::botEmojiStatusEmoji,
- st::defaultPopupMenu,
- Core::TextContext({ .session = &peer->session() })),
- style::margins(st::normalFont->spacew, 0, 0, 0));
- emoji->entity()->resizeToWidth(emoji->entity()->textMaxWidth());
- auto result = Info::BotStarRef::MakePeerBubbleButton(
- parent,
- peer,
- emoji);
- result->setAttribute(Qt::WA_TransparentForMouseEvents);
- return result;
- }
- bool CheckEmojiStatusPremium(not_null<UserData*> bot) {
- if (bot->session().premium()) {
- return true;
- }
- const auto window = ChatHelpers::ResolveWindowDefault()(
- &bot->session());
- if (window) {
- ShowPremiumPreviewBox(window, PremiumFeature::EmojiStatus);
- window->window().activate();
- }
- return false;
- }
- void ConfirmEmojiStatusAccessBox(
- not_null<Ui::GenericBox*> box,
- not_null<UserData*> bot,
- Fn<void(bool)> done) {
- box->setNoContentMargin(true);
- const auto set = box->lifetime().make_state<bool>();
- box->addTopButton(st::boxTitleClose, [=] {
- box->closeBox();
- });
- AddSkip(box->verticalLayout(), 4 * st::defaultVerticalListSkip);
- const auto statusIcon = ChatHelpers::GenerateLocalTgsSticker(
- &bot->session(),
- u"hello_status"_q);
- statusIcon->overrideEmojiUsesTextColor(true);
- auto ownedSet = MakeEmojiSetStatusPreview(
- box,
- bot->session().user(),
- statusIcon);
- box->addRow(
- object_ptr<Ui::RpWidget>::fromRaw(ownedSet.release()));
- AddSkip(box->verticalLayout(), 2 * st::defaultVerticalListSkip);
- auto name = Ui::Text::Bold(bot->name());
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_bot_emoji_status_access_text(
- lt_bot,
- rpl::single(name),
- lt_name,
- rpl::single(name),
- Ui::Text::RichLangValue),
- st::botEmojiStatusText));
- box->addButton(tr::lng_bot_emoji_status_access_allow(), [=] {
- if (!CheckEmojiStatusPremium(bot)) {
- return;
- }
- *set = true;
- box->closeBox();
- done(true);
- });
- box->addButton(tr::lng_cancel(), [=] {
- const auto was = *set;
- box->closeBox();
- if (!was) {
- done(false);
- }
- });
- }
- void ConfirmEmojiStatusBox(
- not_null<Ui::GenericBox*> box,
- not_null<UserData*> bot,
- not_null<DocumentData*> document,
- TimeId duration,
- Fn<void(bool)> done) {
- box->setNoContentMargin(true);
- auto owned = Settings::MakeEmojiStatusPreview(box, document);
- const auto preview = box->addRow(
- object_ptr<Ui::RpWidget>::fromRaw(owned.release()));
- preview->resize(preview->width(), st::botEmojiStatusPreviewHeight);
- const auto set = box->lifetime().make_state<bool>();
- box->addTopButton(st::boxTitleClose, [=] {
- box->closeBox();
- });
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_bot_emoji_status_title(),
- st::botEmojiStatusTitle));
- AddSkip(box->verticalLayout());
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_bot_emoji_status_text(
- lt_bot,
- rpl::single(Ui::Text::Bold(bot->name())),
- Ui::Text::RichLangValue),
- st::botEmojiStatusText));
- AddSkip(box->verticalLayout(), 2 * st::defaultVerticalListSkip);
- auto ownedSet = MakeEmojiSetStatusPreview(
- box,
- document->session().user(),
- document);
- box->addRow(
- object_ptr<Ui::RpWidget>::fromRaw(ownedSet.release()));
- box->addButton(tr::lng_bot_emoji_status_confirm(), [=] {
- if (!CheckEmojiStatusPremium(bot)) {
- return;
- }
- document->owner().emojiStatuses().set(
- { document->id },
- duration ? (base::unixtime::now() + duration) : 0);
- *set = true;
- box->closeBox();
- done(true);
- });
- box->addButton(tr::lng_cancel(), [=] {
- box->closeBox();
- });
- box->boxClosing() | rpl::start_with_next([=] {
- if (!*set) {
- done(false);
- }
- }, box->lifetime());
- }
- class BotAction final : public Ui::Menu::ItemBase {
- public:
- BotAction(
- not_null<Ui::RpWidget*> parent,
- std::shared_ptr<Ui::Show> show,
- const style::Menu &st,
- const AttachWebViewBot &bot,
- Fn<void()> callback);
- bool isEnabled() const override;
- not_null<QAction*> action() const override;
- [[nodiscard]] rpl::producer<bool> forceShown() const;
- void handleKeyPress(not_null<QKeyEvent*> e) override;
- private:
- void contextMenuEvent(QContextMenuEvent *e) override;
- QPoint prepareRippleStartPosition() const override;
- QImage prepareRippleMask() const override;
- int contentHeight() const override;
- void prepare();
- void paint(Painter &p);
- const std::shared_ptr<Ui::Show> _show;
- const not_null<QAction*> _dummyAction;
- const style::Menu &_st;
- const AttachWebViewBot _bot;
- MenuBotIcon _icon;
- base::unique_qptr<Ui::PopupMenu> _menu;
- rpl::event_stream<bool> _forceShown;
- Ui::Text::String _text;
- int _textWidth = 0;
- const int _height;
- };
- BotAction::BotAction(
- not_null<Ui::RpWidget*> parent,
- std::shared_ptr<Ui::Show> show,
- const style::Menu &st,
- const AttachWebViewBot &bot,
- Fn<void()> callback)
- : ItemBase(parent, st)
- , _show(std::move(show))
- , _dummyAction(new QAction(parent))
- , _st(st)
- , _bot(bot)
- , _icon(this, _bot.media)
- , _height(_st.itemPadding.top()
- + _st.itemStyle.font->height
- + _st.itemPadding.bottom()) {
- setAcceptBoth(false);
- initResizeHook(parent->sizeValue());
- setClickedCallback(std::move(callback));
- _icon.move(_st.itemIconPosition);
- paintRequest(
- ) | rpl::start_with_next([=] {
- Painter p(this);
- paint(p);
- }, lifetime());
- enableMouseSelecting();
- prepare();
- }
- void BotAction::paint(Painter &p) {
- const auto selected = isSelected();
- if (selected && _st.itemBgOver->c.alpha() < 255) {
- p.fillRect(0, 0, width(), _height, _st.itemBg);
- }
- p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
- if (isEnabled()) {
- paintRipple(p, 0, 0);
- }
- p.setPen(selected ? _st.itemFgOver : _st.itemFg);
- _text.drawLeftElided(
- p,
- _st.itemPadding.left(),
- _st.itemPadding.top(),
- _textWidth,
- width());
- }
- void BotAction::prepare() {
- _text.setMarkedText(_st.itemStyle, { _bot.name });
- const auto textWidth = _text.maxWidth();
- const auto &padding = _st.itemPadding;
- const auto goodWidth = padding.left()
- + textWidth
- + padding.right();
- const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
- _textWidth = w - (goodWidth - textWidth);
- setMinWidth(w);
- update();
- }
- bool BotAction::isEnabled() const {
- return true;
- }
- not_null<QAction*> BotAction::action() const {
- return _dummyAction;
- }
- void BotAction::contextMenuEvent(QContextMenuEvent *e) {
- _menu = nullptr;
- _menu = base::make_unique_q<Ui::PopupMenu>(
- this,
- st::popupMenuWithIcons);
- _menu->addAction(tr::lng_bot_remove_from_menu(tr::now), [=] {
- const auto bot = _bot.user;
- bot->session().attachWebView().removeFromMenu(_show, bot);
- }, &st::menuIconDelete);
- QObject::connect(_menu, &QObject::destroyed, [=] {
- _forceShown.fire(false);
- });
- _forceShown.fire(true);
- _menu->popup(e->globalPos());
- e->accept();
- }
- QPoint BotAction::prepareRippleStartPosition() const {
- return mapFromGlobal(QCursor::pos());
- }
- QImage BotAction::prepareRippleMask() const {
- return Ui::RippleAnimation::RectMask(size());
- }
- int BotAction::contentHeight() const {
- return _height;
- }
- rpl::producer<bool> BotAction::forceShown() const {
- return _forceShown.events();
- }
- void BotAction::handleKeyPress(not_null<QKeyEvent*> e) {
- if (!isSelected()) {
- return;
- }
- const auto key = e->key();
- if (key == Qt::Key_Enter || key == Qt::Key_Return) {
- setClicked(Ui::Menu::TriggeredSource::Keyboard);
- }
- }
- } // namespace
- base::weak_ptr<WebViewInstance> WebViewInstance::PendingActivation;
- MenuBotIcon::MenuBotIcon(
- QWidget *parent,
- std::shared_ptr<Data::DocumentMedia> media)
- : RpWidget(parent)
- , _media(std::move(media)) {
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- _image = QImage();
- update();
- }, lifetime());
- setAttribute(Qt::WA_TransparentForMouseEvents);
- resize(st::menuIconAdmin.size());
- show();
- }
- void MenuBotIcon::paintEvent(QPaintEvent *e) {
- validate();
- if (!_image.isNull()) {
- QPainter(this).drawImage(0, 0, _image);
- }
- }
- void MenuBotIcon::validate() {
- const auto ratio = style::DevicePixelRatio();
- const auto wanted = size() * ratio;
- if (_mask.size() != wanted) {
- if (!_media || !_media->loaded()) {
- return;
- }
- auto icon = QSvgRenderer(_media->bytes());
- _mask = QImage(wanted, QImage::Format_ARGB32_Premultiplied);
- _mask.setDevicePixelRatio(style::DevicePixelRatio());
- _mask.fill(Qt::transparent);
- if (icon.isValid()) {
- auto p = QPainter(&_mask);
- icon.render(&p, rect());
- p.end();
- _mask = Images::Colored(std::move(_mask), Qt::white);
- }
- }
- if (_image.isNull()) {
- _image = style::colorizeImage(_mask, st::menuIconColor);
- }
- }
- bool PeerMatchesTypes(
- not_null<PeerData*> peer,
- not_null<UserData*> bot,
- PeerTypes types) {
- if (const auto user = peer->asUser()) {
- return (user == bot)
- ? (types & PeerType::SameBot)
- : user->isBot()
- ? (types & PeerType::Bot)
- : (types & PeerType::User);
- } else if (peer->isBroadcast()) {
- return (types & PeerType::Broadcast);
- }
- return (types & PeerType::Group);
- }
- PeerTypes ParseChooseTypes(QStringView choose) {
- auto result = PeerTypes();
- for (const auto &entry : choose.split(QChar(' '))) {
- if (entry == u"users"_q) {
- result |= PeerType::User;
- } else if (entry == u"bots"_q) {
- result |= PeerType::Bot;
- } else if (entry == u"groups"_q) {
- result |= PeerType::Group;
- } else if (entry == u"channels"_q) {
- result |= PeerType::Broadcast;
- }
- }
- return result;
- }
- WebViewInstance::WebViewInstance(WebViewDescriptor &&descriptor)
- : _parentShow(descriptor.parentShow
- ? std::move(descriptor.parentShow)
- : descriptor.context.controller
- ? descriptor.context.controller.get()->uiShow()
- : nullptr)
- , _session(&descriptor.bot->session())
- , _bot(descriptor.bot)
- , _context(ResolveContext(_bot, std::move(descriptor.context)))
- , _button(std::move(descriptor.button))
- , _source(std::move(descriptor.source)) {
- resolve();
- }
- WebViewInstance::~WebViewInstance() {
- _session->api().request(base::take(_requestId)).cancel();
- _session->api().request(base::take(_prolongId)).cancel();
- base::take(_panel);
- }
- Main::Session &WebViewInstance::session() const {
- return *_session;
- }
- not_null<UserData*> WebViewInstance::bot() const {
- return _bot;
- }
- WebViewSource WebViewInstance::source() const {
- return _source;
- }
- void WebViewInstance::activate() {
- if (_panel) {
- _panel->requestActivate();
- } else {
- PendingActivation = this;
- }
- }
- void WebViewInstance::resolve() {
- v::match(_source, [&](WebViewSourceButton data) {
- confirmOpen([=] {
- if (data.simple) {
- requestSimple();
- } else {
- requestButton();
- }
- });
- }, [&](WebViewSourceSwitch) {
- confirmOpen([=] {
- requestSimple();
- });
- }, [&](WebViewSourceLinkApp data) {
- resolveApp(
- data.appname,
- data.token,
- (_context.maySkipConfirmation
- ? ConfirmType::None
- : ConfirmType::Always));
- }, [&](WebViewSourceLinkBotProfile) {
- confirmOpen([=] {
- requestMain();
- });
- }, [&](WebViewSourceLinkAttachMenu data) {
- requestWithMenuAdd();
- }, [&](WebViewSourceMainMenu) {
- requestWithMainMenuDisclaimer();
- }, [&](WebViewSourceAttachMenu) {
- requestWithMenuAdd();
- }, [&](WebViewSourceBotMenu) {
- if (!openAppFromBotMenuLink()) {
- confirmOpen([=] {
- requestButton();
- });
- }
- }, [&](WebViewSourceGame game) {
- showGame();
- }, [&](WebViewSourceBotProfile) {
- if (_context.maySkipConfirmation) {
- requestMain();
- } else {
- confirmOpen([=] {
- requestMain();
- });
- }
- });
- }
- bool WebViewInstance::openAppFromBotMenuLink() {
- const auto url = QString::fromUtf8(_button.url);
- const auto local = Core::TryConvertUrlToLocal(url);
- const auto prefix = u"tg://resolve?"_q;
- if (!local.startsWith(prefix)) {
- return false;
- }
- const auto params = qthelp::url_parse_params(
- local.mid(prefix.size()),
- qthelp::UrlParamNameTransform::ToLower);
- const auto domainParam = params.value(u"domain"_q);
- const auto appnameParam = params.value(u"appname"_q);
- const auto webChannelPreviewLink = (domainParam == u"s"_q)
- && !appnameParam.isEmpty();
- const auto appname = webChannelPreviewLink ? QString() : appnameParam;
- if (appname.isEmpty()) {
- return false;
- }
- resolveApp(appname, params.value(u"startapp"_q), ConfirmType::Once);
- return true;
- }
- void WebViewInstance::resolveApp(
- const QString &appname,
- const QString &startparam,
- ConfirmType confirmType) {
- const auto already = _session->data().findBotApp(_bot->id, appname);
- _requestId = _session->api().request(MTPmessages_GetBotApp(
- MTP_inputBotAppShortName(
- _bot->inputUser,
- MTP_string(appname)),
- MTP_long(already ? already->hash : 0)
- )).done([=](const MTPmessages_BotApp &result) {
- _requestId = 0;
- const auto &data = result.data();
- const auto received = _session->data().processBotApp(
- _bot->id,
- data.vapp());
- _app = received ? received : already;
- _appStartParam = startparam;
- if (!_app) {
- _parentShow->showToast(tr::lng_username_app_not_found(tr::now));
- close();
- return;
- }
- const auto confirm = data.is_inactive()
- || (confirmType != ConfirmType::None);
- const auto writeAccess = result.data().is_request_write_access();
- const auto forceConfirmation = data.is_inactive()
- || (confirmType == ConfirmType::Always);
- // Check if this app can be added to main menu.
- // On fail it'll still be opened.
- using Result = AttachWebView::AddToMenuResult;
- const auto done = crl::guard(this, [=](Result value, auto) {
- if (value == Result::Cancelled) {
- close();
- } else if (value != Result::Unsupported) {
- requestApp(true);
- } else if (confirm) {
- confirmAppOpen(writeAccess, [=](bool allowWrite) {
- requestApp(allowWrite);
- }, forceConfirmation);
- } else {
- requestApp(false);
- }
- });
- _session->attachWebView().requestAddToMenu(_bot, done);
- }).fail([=] {
- _parentShow->showToast(tr::lng_username_app_not_found(tr::now));
- close();
- }).send();
- }
- void WebViewInstance::confirmOpen(Fn<void()> done) {
- if (_bot->isVerified()
- || _session->local().isPeerTrustedOpenWebView(_bot->id)) {
- done();
- return;
- }
- const auto callback = [=](Fn<void()> close) {
- _session->local().markPeerTrustedOpenWebView(_bot->id);
- close();
- done();
- };
- const auto cancel = [=](Fn<void()> close) {
- botClose();
- close();
- };
- _parentShow->show(Box([=](not_null<Ui::GenericBox*> box) {
- FillBotUsepic(box, _bot, _context.controller);
- Ui::ConfirmBox(box, {
- .text = tr::lng_profile_open_app_about(
- tr::now,
- lt_terms,
- Ui::Text::Link(
- tr::lng_profile_open_app_terms(tr::now),
- tr::lng_mini_apps_tos_url(tr::now)),
- Ui::Text::RichLangValue),
- .confirmed = crl::guard(this, callback),
- .cancelled = crl::guard(this, cancel),
- .confirmText = tr::lng_view_button_bot_app(),
- });
- }));
- }
- void WebViewInstance::confirmAppOpen(
- bool writeAccess,
- Fn<void(bool allowWrite)> done,
- bool forceConfirmation) {
- if (!forceConfirmation
- && (_bot->isVerified()
- || _session->local().isPeerTrustedOpenWebView(_bot->id))) {
- done(writeAccess);
- return;
- }
- _parentShow->show(Box([=](not_null<Ui::GenericBox*> box) {
- const auto allowed = std::make_shared<Ui::Checkbox*>();
- const auto callback = [=](Fn<void()> close) {
- _session->local().markPeerTrustedOpenWebView(_bot->id);
- done((*allowed) && (*allowed)->checked());
- close();
- };
- const auto cancelled = [=](Fn<void()> close) {
- botClose();
- close();
- };
- FillBotUsepic(box, _bot, _context.controller);
- Ui::ConfirmBox(box, {
- tr::lng_profile_open_app_about(
- tr::now,
- lt_terms,
- Ui::Text::Link(
- tr::lng_profile_open_app_terms(tr::now),
- tr::lng_mini_apps_tos_url(tr::now)),
- Ui::Text::RichLangValue),
- crl::guard(this, callback),
- crl::guard(this, cancelled),
- tr::lng_view_button_bot_app(),
- });
- if (writeAccess) {
- (*allowed) = box->addRow(
- object_ptr<Ui::Checkbox>(
- box,
- tr::lng_url_auth_allow_messages(
- tr::now,
- lt_bot,
- Ui::Text::Bold(_bot->name()),
- Ui::Text::WithEntities),
- true,
- st::urlAuthCheckbox),
- style::margins(
- st::boxRowPadding.left(),
- st::boxPhotoCaptionSkip,
- st::boxRowPadding.right(),
- st::boxPhotoCaptionSkip));
- (*allowed)->setAllowTextLines();
- }
- }));
- }
- void WebViewInstance::requestButton() {
- Expects(_context.action.has_value());
- const auto &action = *_context.action;
- using Flag = MTPmessages_RequestWebView::Flag;
- _requestId = _session->api().request(MTPmessages_RequestWebView(
- MTP_flags(Flag::f_theme_params
- | (_context.fullscreen ? Flag::f_fullscreen : Flag(0))
- | (_button.url.isEmpty() ? Flag(0) : Flag::f_url)
- | (_button.startCommand.isEmpty()
- ? Flag(0)
- : Flag::f_start_param)
- | (v::is<WebViewSourceBotMenu>(_source)
- ? Flag::f_from_bot_menu
- : Flag(0))
- | (action.replyTo ? Flag::f_reply_to : Flag(0))
- | (action.options.sendAs ? Flag::f_send_as : Flag(0))
- | (action.options.silent ? Flag::f_silent : Flag(0))),
- action.history->peer->input,
- _bot->inputUser,
- MTP_bytes(_button.url),
- MTP_string(_button.startCommand),
- MTP_dataJSON(MTP_bytes(botThemeParams().json)),
- MTP_string("tdesktop"),
- action.mtpReplyTo(),
- (action.options.sendAs
- ? action.options.sendAs->input
- : MTP_inputPeerEmpty())
- )).done([=](const MTPWebViewResult &result) {
- const auto &data = result.data();
- show({
- .url = qs(data.vurl()),
- .queryId = data.vquery_id().value_or_empty(),
- .fullscreen = data.is_fullscreen(),
- });
- }).fail([=](const MTP::Error &error) {
- _parentShow->showToast(error.type());
- if (error.type() == u"BOT_INVALID"_q) {
- _session->attachWebView().requestBots();
- }
- close();
- }).send();
- }
- void WebViewInstance::requestSimple() {
- using Flag = MTPmessages_RequestSimpleWebView::Flag;
- _requestId = _session->api().request(MTPmessages_RequestSimpleWebView(
- MTP_flags(Flag::f_theme_params
- | (_context.fullscreen ? Flag::f_fullscreen : Flag(0))
- | (v::is<WebViewSourceSwitch>(_source)
- ? (Flag::f_url | Flag::f_from_switch_webview)
- : v::is<WebViewSourceMainMenu>(_source)
- ? (Flag::f_from_side_menu
- | (_button.startCommand.isEmpty() // from LinkMainMenu
- ? Flag()
- : Flag::f_start_param))
- : Flag::f_url)),
- _bot->inputUser,
- MTP_bytes(_button.url),
- MTP_string(_button.startCommand),
- MTP_dataJSON(MTP_bytes(botThemeParams().json)),
- MTP_string("tdesktop")
- )).done([=](const MTPWebViewResult &result) {
- const auto &data = result.data();
- show({
- .url = qs(data.vurl()),
- .fullscreen = data.is_fullscreen(),
- });
- }).fail([=](const MTP::Error &error) {
- _parentShow->showToast(error.type());
- close();
- }).send();
- }
- void WebViewInstance::requestMain() {
- using Flag = MTPmessages_RequestMainWebView::Flag;
- _requestId = _session->api().request(MTPmessages_RequestMainWebView(
- MTP_flags(Flag::f_theme_params
- | (_context.fullscreen ? Flag::f_fullscreen : Flag(0))
- | (_button.startCommand.isEmpty()
- ? Flag()
- : Flag::f_start_param)
- | (v::is<WebViewSourceLinkBotProfile>(_source)
- ? (v::get<WebViewSourceLinkBotProfile>(_source).compact
- ? Flag::f_compact
- : Flag(0))
- : Flag(0))),
- _context.action->history->peer->input,
- _bot->inputUser,
- MTP_string(_button.startCommand),
- MTP_dataJSON(MTP_bytes(botThemeParams().json)),
- MTP_string("tdesktop")
- )).done([=](const MTPWebViewResult &result) {
- const auto &data = result.data();
- show({
- .url = qs(data.vurl()),
- .fullscreen = data.is_fullscreen(),
- });
- }).fail([=](const MTP::Error &error) {
- _parentShow->showToast(error.type());
- close();
- }).send();
- }
- void WebViewInstance::requestApp(bool allowWrite) {
- Expects(_app != nullptr);
- Expects(_context.action.has_value());
- using Flag = MTPmessages_RequestAppWebView::Flag;
- const auto app = _app;
- const auto title = app->title;
- const auto flags = Flag::f_theme_params
- | (_context.fullscreen ? Flag::f_fullscreen : Flag(0))
- | (_appStartParam.isEmpty() ? Flag(0) : Flag::f_start_param)
- | (allowWrite ? Flag::f_write_allowed : Flag(0));
- _requestId = _session->api().request(MTPmessages_RequestAppWebView(
- MTP_flags(flags),
- _context.action->history->peer->input,
- MTP_inputBotAppID(MTP_long(app->id), MTP_long(app->accessHash)),
- MTP_string(_appStartParam),
- MTP_dataJSON(MTP_bytes(botThemeParams().json)),
- MTP_string("tdesktop")
- )).done([=](const MTPWebViewResult &result) {
- _requestId = 0;
- const auto &data = result.data();
- show({
- .url = qs(data.vurl()),
- .title = title,
- .fullscreen = data.is_fullscreen(),
- });
- }).fail([=](const MTP::Error &error) {
- _requestId = 0;
- if (error.type() == u"BOT_INVALID"_q) {
- _session->attachWebView().requestBots();
- }
- close();
- }).send();
- }
- void WebViewInstance::requestWithMainMenuDisclaimer() {
- using Result = AttachWebView::AddToMenuResult;
- const auto done = crl::guard(this, [=](Result value, auto) {
- if (value == Result::Cancelled) {
- close();
- } else if (value == Result::Unsupported) {
- _parentShow->showToast(tr::lng_bot_menu_not_supported(tr::now));
- close();
- } else {
- requestSimple();
- }
- });
- _session->attachWebView().acceptMainMenuDisclaimer(
- _parentShow,
- _bot,
- done);
- }
- void WebViewInstance::requestWithMenuAdd() {
- using Result = AttachWebView::AddToMenuResult;
- const auto done = crl::guard(this, [=](Result value, PeerTypes types) {
- if (value == Result::Cancelled) {
- close();
- } else if (value == Result::Unsupported) {
- _parentShow->showToast(tr::lng_bot_menu_not_supported(tr::now));
- close();
- } else if (v::is<WebViewSourceLinkAttachMenu>(_source)) {
- maybeChooseAndRequestButton(types);
- } else if (v::is<WebViewSourceAttachMenu>(_source)) {
- requestButton();
- } else {
- requestSimple();
- }
- });
- _session->attachWebView().requestAddToMenu(_bot, done);
- }
- void WebViewInstance::maybeChooseAndRequestButton(PeerTypes supported) {
- Expects(v::is<WebViewSourceLinkAttachMenu>(_source));
- const auto link = v::get<WebViewSourceLinkAttachMenu>(_source);
- const auto chooseFrom = (link.choose & supported);
- if (!chooseFrom) {
- requestButton();
- return;
- }
- const auto bot = _bot;
- const auto button = _button;
- const auto weak = _context.controller;
- const auto done = [=](not_null<Data::Thread*> thread) {
- if (const auto controller = WindowForThread(weak, thread)) {
- thread->session().attachWebView().open({
- .bot = bot,
- .context = {
- .controller = controller,
- .action = Api::SendAction(thread),
- },
- .button = button,
- .source = InlineBots::WebViewSourceLinkAttachMenu{
- .thread = thread,
- .token = button.startCommand,
- },
- });
- }
- };
- ShowChooseBox(_parentShow, _session, chooseFrom, done);
- close();
- }
- void WebViewInstance::show(ShowArgs &&args) {
- auto title = args.title.isEmpty()
- ? Info::Profile::NameValue(_bot)
- : rpl::single(args.title);
- auto titleBadge = _bot->isVerified()
- ? object_ptr<Ui::RpWidget>(_parentShow->toastParent())
- : nullptr;
- if (titleBadge) {
- const auto raw = titleBadge.data();
- raw->paintRequest() | rpl::start_with_next([=] {
- auto p = Painter(raw);
- st::infoVerifiedCheck.paint(p, st::lineWidth, 0, raw->width());
- }, raw->lifetime());
- raw->resize(st::infoVerifiedCheck.size() + QSize(0, st::lineWidth));
- }
- const auto &bots = _session->attachWebView().attachBots();
- using Button = Ui::BotWebView::MenuButton;
- const auto attached = ranges::find(
- bots,
- not_null{ _bot },
- &AttachWebViewBot::user);
- const auto hasOpenBot = v::is<WebViewSourceMainMenu>(_source)
- || (_context.action->history->peer != _bot);
- const auto hasRemoveFromMenu = (attached != end(bots))
- && (!attached->inactive || attached->inMainMenu)
- && (v::is<WebViewSourceMainMenu>(_source)
- || v::is<WebViewSourceAttachMenu>(_source)
- || v::is<WebViewSourceLinkAttachMenu>(_source));
- const auto buttons = (hasOpenBot ? Button::OpenBot : Button::None)
- | (!hasRemoveFromMenu
- ? Button::None
- : attached->inMainMenu
- ? Button::RemoveFromMainMenu
- : Button::RemoveFromMenu);
- const auto allowClipboardRead = v::is<WebViewSourceMainMenu>(_source)
- || v::is<WebViewSourceAttachMenu>(_source)
- || (attached != end(bots)
- && (attached->inAttachMenu || attached->inMainMenu));
- const auto downloads = &_session->attachWebView().downloads();
- _panelUrl = args.url;
- _panel = Ui::BotWebView::Show({
- .url = args.url,
- .storageId = _session->local().resolveStorageIdBots(),
- .title = std::move(title),
- .titleBadge = std::move(titleBadge),
- .bottom = rpl::single('@' + _bot->username()),
- .delegate = static_cast<Ui::BotWebView::Delegate*>(this),
- .menuButtons = buttons,
- .fullscreen = args.fullscreen,
- .allowClipboardRead = allowClipboardRead,
- .downloadsProgress = downloads->progress(_bot),
- });
- started(args.queryId);
- if (const auto strong = PendingActivation.get()) {
- if (strong == this) {
- PendingActivation = nullptr;
- _panel->requestActivate();
- }
- }
- }
- void WebViewInstance::showGame() {
- Expects(v::is<WebViewSourceGame>(_source));
- const auto game = v::get<WebViewSourceGame>(_source);
- _panelUrl = QString::fromUtf8(_button.url);
- _panel = Ui::BotWebView::Show({
- .url = _panelUrl,
- .storageId = _session->local().resolveStorageIdBots(),
- .title = rpl::single(game.title),
- .bottom = rpl::single('@' + _bot->username()),
- .delegate = static_cast<Ui::BotWebView::Delegate*>(this),
- .menuButtons = Ui::BotWebView::MenuButton::ShareGame,
- });
- }
- void WebViewInstance::close() {
- _session->attachWebView().close(this);
- }
- void WebViewInstance::started(uint64 queryId) {
- Expects(_context.action.has_value());
- if (!queryId) {
- return;
- }
- _session->data().webViewResultSent(
- ) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) {
- return (sent.queryId == queryId);
- }) | rpl::start_with_next([=] {
- close();
- }, _panel->lifetime());
- const auto action = *_context.action;
- base::timer_each(
- kProlongTimeout
- ) | rpl::start_with_next([=] {
- using Flag = MTPmessages_ProlongWebView::Flag;
- _session->api().request(base::take(_prolongId)).cancel();
- _prolongId = _session->api().request(MTPmessages_ProlongWebView(
- MTP_flags(Flag(0)
- | (action.replyTo ? Flag::f_reply_to : Flag(0))
- | (action.options.sendAs ? Flag::f_send_as : Flag(0))
- | (action.options.silent ? Flag::f_silent : Flag(0))),
- action.history->peer->input,
- _bot->inputUser,
- MTP_long(queryId),
- action.mtpReplyTo(),
- (action.options.sendAs
- ? action.options.sendAs->input
- : MTP_inputPeerEmpty())
- )).done([=] {
- _prolongId = 0;
- }).send();
- }, _panel->lifetime());
- }
- Webview::ThemeParams WebViewInstance::botThemeParams() {
- auto result = Window::Theme::WebViewParams();
- if (const auto info = _bot->botInfo.get()) {
- const auto night = Window::Theme::IsNightMode();
- const auto &title = night
- ? info->botAppColorTitleNight
- : info->botAppColorTitleDay;
- const auto &body = night
- ? info->botAppColorBodyNight
- : info->botAppColorBodyDay;
- if (title.alpha() == 255) {
- result.titleBg = title;
- }
- if (body.alpha() == 255) {
- result.bodyBg = body;
- }
- }
- return result;
- }
- auto WebViewInstance::botDownloads(bool forceCheck)
- -> const std::vector<Ui::BotWebView::DownloadsEntry> & {
- return _session->attachWebView().downloads().list(_bot, forceCheck);
- }
- void WebViewInstance::botDownloadsAction(
- uint32 id,
- Ui::BotWebView::DownloadsAction type) {
- _session->attachWebView().downloads().action(_bot, id, type);
- }
- bool WebViewInstance::botHandleLocalUri(QString uri, bool keepOpen) {
- const auto local = Core::TryConvertUrlToLocal(uri);
- if (Core::InternalPassportLink(local)) {
- return true;
- } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive)
- && !local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) {
- return false;
- }
- const auto bot = _bot;
- const auto context = std::make_shared<WebViewContext>(_context);
- if (!keepOpen) {
- botClose();
- }
- crl::on_main([=] {
- if (bot->session().windows().empty()) {
- Core::App().domain().activate(&bot->session().account());
- }
- const auto window = !bot->session().windows().empty()
- ? bot->session().windows().front().get()
- : nullptr;
- context->controller = window;
- const auto variant = QVariant::fromValue(ClickHandlerContext{
- .sessionWindow = window,
- .botWebviewContext = context,
- });
- UrlClickHandler::Open(local, variant);
- });
- return true;
- }
- void WebViewInstance::botHandleInvoice(QString slug) {
- Expects(_panel != nullptr);
- using Result = Payments::CheckoutResult;
- const auto weak = base::make_weak(_panel.get());
- const auto reactivate = [=](Result result) {
- if (const auto strong = weak.get()) {
- strong->invoiceClosed(slug, [&] {
- switch (result) {
- case Result::Paid: return "paid";
- case Result::Failed: return "failed";
- case Result::Pending: return "pending";
- case Result::Cancelled: return "cancelled";
- }
- Unexpected("Payments::CheckoutResult value.");
- }());
- }
- };
- Payments::CheckoutProcess::Start(
- &_bot->session(),
- slug,
- reactivate,
- nonPanelPaymentFormFactory(reactivate));
- }
- auto WebViewInstance::nonPanelPaymentFormFactory(
- Fn<void(Payments::CheckoutResult)> reactivate)
- -> Fn<void(Payments::NonPanelPaymentForm)> {
- using namespace Payments;
- const auto panel = base::make_weak(_panel.get());
- const auto weak = _context.controller;
- return [=](Payments::NonPanelPaymentForm form) {
- using CreditsFormDataPtr = std::shared_ptr<CreditsFormData>;
- using CreditsReceiptPtr = std::shared_ptr<CreditsReceiptData>;
- v::match(form, [&](const CreditsFormDataPtr &form) {
- if (const auto strong = panel.get()) {
- ProcessCreditsPayment(
- uiShow(),
- strong->toastParent().get(),
- form,
- reactivate);
- }
- }, [&](const CreditsReceiptPtr &receipt) {
- if (const auto controller = weak.get()) {
- ProcessCreditsReceipt(controller, receipt, reactivate);
- }
- }, [&](RealFormPresentedNotification) {
- _panel->hideForPayment();
- });
- };
- }
- void WebViewInstance::botHandleMenuButton(
- Ui::BotWebView::MenuButton button) {
- Expects(_panel != nullptr);
- using Button = Ui::BotWebView::MenuButton;
- const auto bot = _bot;
- switch (button) {
- case Button::OpenBot:
- botClose();
- if (bot->session().windows().empty()) {
- Core::App().domain().activate(&bot->session().account());
- }
- if (!bot->session().windows().empty()) {
- const auto window = bot->session().windows().front();
- window->showPeerHistory(bot);
- window->window().activate();
- }
- break;
- case Button::RemoveFromMenu:
- case Button::RemoveFromMainMenu: {
- const auto &bots = _session->attachWebView().attachBots();
- const auto attached = ranges::find(
- bots,
- _bot,
- &AttachWebViewBot::user);
- const auto name = (attached != end(bots))
- ? attached->name
- : _bot->name();
- const auto done = crl::guard(this, [=] {
- const auto session = _session;
- const auto was = _parentShow;
- botClose();
- const auto active = Core::App().activeWindow();
- const auto show = active ? active->uiShow() : was;
- session->attachWebView().removeFromMenu(show, bot);
- if (active) {
- active->activate();
- }
- });
- const auto main = (button == Button::RemoveFromMainMenu);
- _panel->showBox(Ui::MakeConfirmBox({
- (main
- ? tr::lng_bot_remove_from_side_menu_sure
- : tr::lng_bot_remove_from_menu_sure)(
- tr::now,
- lt_bot,
- Ui::Text::Bold(name),
- Ui::Text::WithEntities),
- done,
- }));
- } break;
- case Button::ShareGame: {
- const auto itemId = v::is<WebViewSourceGame>(_source)
- ? v::get<WebViewSourceGame>(_source).messageId
- : FullMsgId();
- if (!_panel || !itemId) {
- return;
- } else if (const auto item = _session->data().message(itemId)) {
- FastShareMessage(uiShow(), item);
- } else {
- _panel->showToast({ tr::lng_message_not_found(tr::now) });
- }
- } break;
- }
- }
- bool WebViewInstance::botValidateExternalLink(QString uri) {
- const auto lower = uri.toLower();
- const auto allowed = _session->appConfig().get<std::vector<QString>>(
- "web_app_allowed_protocols",
- std::vector{ u"http"_q, u"https"_q });
- for (const auto &protocol : allowed) {
- if (lower.startsWith(protocol + u"://"_q)) {
- return true;
- }
- }
- return false;
- }
- void WebViewInstance::botOpenIvLink(QString uri) {
- const auto window = _context.controller.get();
- if (window) {
- Core::App().iv().openWithIvPreferred(window, uri);
- } else {
- Core::App().iv().openWithIvPreferred(_session, uri);
- }
- }
- void WebViewInstance::botSendData(QByteArray data) {
- Expects(_context.action.has_value());
- const auto button = std::get_if<WebViewSourceButton>(&_source);
- if (!button
- || !button->simple
- || _context.action->history->peer != _bot
- || _dataSent) {
- return;
- }
- _dataSent = true;
- _session->api().request(MTPmessages_SendWebViewData(
- _bot->inputUser,
- MTP_long(base::RandomValue<uint64>()),
- MTP_string(_button.text),
- MTP_bytes(data)
- )).done([session = _session](const MTPUpdates &result) {
- session->api().applyUpdates(result);
- }).send();
- botClose();
- }
- void WebViewInstance::botSwitchInlineQuery(
- std::vector<QString> chatTypes,
- QString query) {
- const auto controller = _context.controller.get();
- const auto types = PeerTypesFromNames(chatTypes);
- if (!_bot
- || !_bot->isBot()
- || _bot->botInfo->inlinePlaceholder.isEmpty()
- || !controller) {
- return;
- } else if (!types) {
- if (_context.dialogsEntryState.key.owningHistory()) {
- controller->switchInlineQuery(
- _context.dialogsEntryState,
- _bot,
- query);
- }
- } else {
- const auto bot = _bot;
- const auto done = [=](not_null<Data::Thread*> thread) {
- controller->switchInlineQuery(thread, bot, query);
- };
- ShowChooseBox(
- controller,
- types,
- done,
- tr::lng_inline_switch_choose());
- }
- botClose();
- }
- void WebViewInstance::botCheckWriteAccess(Fn<void(bool allowed)> callback) {
- _session->api().request(MTPbots_CanSendMessage(
- _bot->inputUser
- )).done([=](const MTPBool &result) {
- callback(mtpIsTrue(result));
- }).fail([=] {
- callback(false);
- }).send();
- }
- void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) {
- _session->api().request(MTPbots_AllowSendMessage(
- _bot->inputUser
- )).done([session = _session, callback](const MTPUpdates &result) {
- session->api().applyUpdates(result);
- callback(true);
- }).fail([=] {
- callback(false);
- }).send();
- }
- void WebViewInstance::botRequestEmojiStatusAccess(
- Fn<void(bool allowed)> callback) {
- if (_bot->botInfo->canManageEmojiStatus) {
- callback(true);
- } else if (const auto panel = _panel.get()) {
- const auto bot = _bot;
- panel->showBox(Box(ConfirmEmojiStatusAccessBox, bot, [=](bool ok) {
- if (!ok) {
- callback(false);
- return;
- }
- const auto session = &bot->session();
- bot->botInfo->canManageEmojiStatus = true;
- session->api().request(MTPbots_ToggleUserEmojiStatusPermission(
- bot->inputUser,
- MTP_bool(true)
- )).done([=] {
- callback(true);
- }).fail([=] {
- callback(false);
- }).send();
- }));
- } else {
- callback(false);
- }
- }
- void WebViewInstance::botSharePhone(Fn<void(bool shared)> callback) {
- const auto history = _bot->owner().history(_bot);
- if (_bot->isBlocked()) {
- const auto done = crl::guard(this, [=](bool success) {
- if (success) {
- botSharePhone(callback);
- } else {
- callback(false);
- }
- });
- _session->api().blockedPeers().unblock(_bot, done);
- return;
- }
- auto action = Api::SendAction(history);
- action.clearDraft = false;
- _session->api().shareContact(
- _session->user(),
- action,
- std::move(callback));
- }
- void WebViewInstance::botInvokeCustomMethod(
- Ui::BotWebView::CustomMethodRequest request) {
- const auto callback = request.callback;
- _session->api().request(MTPbots_InvokeWebViewCustomMethod(
- _bot->inputUser,
- MTP_string(request.method),
- MTP_dataJSON(MTP_bytes(request.params))
- )).done([=](const MTPDataJSON &result) {
- callback(result.data().vdata().v);
- }).fail([=](const MTP::Error &error) {
- callback(base::make_unexpected(error.type()));
- }).send();
- }
- void WebViewInstance::botSendPreparedMessage(
- Ui::BotWebView::SendPreparedMessageRequest request) {
- const auto bot = _bot;
- const auto id = request.id;
- const auto panel = _panel.get();
- const auto weak = base::make_weak(panel);
- const auto callback = request.callback;
- if (!panel) {
- callback(u"UNKNOWN_ERROR"_q);
- return;
- }
- _session->api().request(MTPmessages_GetPreparedInlineMessage(
- bot->inputUser,
- MTP_string(request.id)
- )).done([=](const MTPmessages_PreparedInlineMessage &result) {
- const auto panel = weak.get();
- const auto &data = result.data();
- bot->owner().processUsers(data.vusers());
- const auto parsed = std::shared_ptr<Result>(Result::Create(
- &bot->session(),
- data.vquery_id().v,
- data.vresult()));
- if (!parsed || !panel) {
- callback(u"UNKNOWN_ERROR"_q);
- return;
- }
- const auto types = PeerTypesFromMTP(data.vpeer_types());
- const auto history = bot->owner().history(bot->session().user());
- const auto item = parsed->makeMessage(history, {
- .id = bot->owner().nextNonHistoryEntryId(),
- .flags = MessageFlag::FakeHistoryItem,
- .from = bot->session().userPeerId(),
- .date = base::unixtime::now(),
- .viaBotId = peerToUser(bot->id),
- });
- struct State {
- QPointer<Ui::BoxContent> preview;
- QPointer<Ui::BoxContent> choose;
- rpl::event_stream<not_null<Data::Thread*>> recipient;
- Fn<void(Api::SendOptions)> send;
- SendPaymentHelper sendPayment;
- bool sent = false;
- };
- const auto state = std::make_shared<State>();
- auto recipient = state->recipient.events();
- const auto send = [=](
- std::vector<not_null<Data::Thread*>> list,
- Api::SendOptions options) {
- if (state->sent) {
- return;
- }
- state->sent = true;
- const auto failed = std::make_shared<int>();
- const auto count = int(list.size());
- const auto weak1 = state->preview;
- const auto weak2 = state->choose;
- const auto close = [=] {
- if (const auto strong = weak1.data()) {
- strong->closeBox();
- }
- if (const auto strong = weak2.data()) {
- strong->closeBox();
- }
- };
- const auto done = [=](bool success) {
- if (*failed < 0) {
- return;
- }
- if (success) {
- *failed = -1;
- if (const auto strong2 = weak2.data()) {
- strong2->showToast({ tr::lng_share_done(tr::now) });
- } else if (const auto strong1 = weak1.data()) {
- strong1->showToast({ tr::lng_share_done(tr::now) });
- }
- base::call_delayed(Ui::Toast::kDefaultDuration, close);
- callback(QString());
- } else if (++*failed == count) {
- close();
- callback(u"MESSAGE_SEND_FAILED"_q);
- }
- };
- for (const auto &thread : list) {
- bot->session().api().sendInlineResult(
- bot,
- parsed.get(),
- Api::SendAction(thread, options),
- std::nullopt,
- done);
- }
- };
- auto box = Box(PreparedPreviewBox, item, std::move(recipient), [=] {
- if (state->sent) {
- return;
- }
- const auto chosen = [=](not_null<Data::Thread*> thread) {
- if (!Data::CanSend(thread, ChatRestriction::SendInline)) {
- panel->showToast({
- tr::lng_restricted_send_inline_all(tr::now),
- });
- return false;
- }
- state->recipient.fire_copy(thread);
- return true;
- };
- auto box = Window::PrepareChooseRecipientBox(
- &bot->session(),
- chosen,
- tr::lng_inline_switch_choose(),
- nullptr,
- types,
- send);
- state->choose = box.data();
- panel->showBox(std::move(box));
- }, [=](not_null<Data::Thread*> thread) {
- const auto weak = base::make_weak(thread);
- state->send = [=](Api::SendOptions options) {
- const auto strong = weak.get();
- if (!strong) {
- state->send = nullptr;
- return;
- }
- const auto withPaymentApproved = [=](int stars) {
- if (const auto onstack = state->send) {
- auto copy = options;
- copy.starsApproved = stars;
- onstack(copy);
- }
- };
- const auto checked = state->sendPayment.check(
- uiShow(),
- strong->peer(),
- 1,
- options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- state->send = nullptr;
- send({ strong }, options);
- };
- state->send({});
- });
- box->boxClosing() | rpl::start_with_next([=] {
- if (!state->sent) {
- callback("USER_DECLINED");
- }
- }, box->lifetime());
- state->preview = box.data();
- panel->showBox(std::move(box));
- }).fail([=] {
- callback(u"MESSAGE_EXPIRED"_q);
- }).send();
- }
- void WebViewInstance::botSetEmojiStatus(
- Ui::BotWebView::SetEmojiStatusRequest request) {
- const auto bot = _bot;
- const auto panel = _panel.get();
- const auto callback = request.callback;
- const auto duration = request.duration;
- if (!panel) {
- callback(u"UNKNOWN_ERROR"_q);
- return;
- }
- _session->data().customEmojiManager().resolve(
- request.customEmojiId
- ) | rpl::start_with_next_error([=](not_null<DocumentData*> document) {
- const auto sticker = document->sticker();
- if (!sticker || sticker->setType != Data::StickersType::Emoji) {
- callback(u"SUGGESTED_EMOJI_INVALID"_q);
- return;
- }
- const auto done = [=](bool success) {
- callback(success ? QString() : u"USER_DECLINED"_q);
- };
- panel->showBox(
- Box(ConfirmEmojiStatusBox, bot, document, duration, done));
- }, [=] { callback(u"SUGGESTED_EMOJI_INVALID"_q); }, panel->lifetime());
- }
- void WebViewInstance::botDownloadFile(
- Ui::BotWebView::DownloadFileRequest request) {
- const auto callback = request.callback;
- if (_confirmingDownload || !_panel) {
- callback(false);
- return;
- }
- _confirmingDownload = true;
- const auto done = [=](QString path) {
- _confirmingDownload = false;
- if (path.isEmpty()) {
- callback(false);
- return;
- }
- _bot->session().attachWebView().downloads().start({
- .bot = _bot,
- .url = request.url,
- .path = path,
- });
- callback(true);
- };
- _bot->session().api().request(MTPbots_CheckDownloadFileParams(
- _bot->inputUser,
- MTP_string(request.name),
- MTP_string(request.url)
- )).done([=] {
- _panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{
- .session = &_bot->session(),
- .bot = _bot->name(),
- .name = base::FileNameFromUserString(request.name),
- .url = request.url,
- .done = done,
- }));
- }).fail([=] {
- done(QString());
- }).send();
- }
- void WebViewInstance::botOpenPrivacyPolicy() {
- const auto bot = _bot;
- const auto weak = _context.controller;
- const auto command = u"privacy"_q;
- const auto findCommand = [=] {
- if (!bot->isBot()) {
- return QString();
- }
- for (const auto &data : bot->botInfo->commands) {
- const auto isSame = !data.command.compare(
- command,
- Qt::CaseInsensitive);
- if (isSame) {
- return data.command;
- }
- }
- return QString();
- };
- const auto makeOtherContext = [=](bool forceWindow) {
- return QVariant::fromValue(ClickHandlerContext{
- .sessionWindow = (forceWindow
- ? WindowForThread(weak, bot->owner().history(bot))
- : weak),
- .peer = bot,
- });
- };
- const auto sendCommand = [=] {
- const auto original = findCommand();
- if (original.isEmpty()) {
- return false;
- }
- BotCommandClickHandler('/' + original).onClick(ClickContext{
- Qt::LeftButton,
- makeOtherContext(true)
- });
- return true;
- };
- const auto openUrl = [=](const QString &url) {
- Core::App().iv().openWithIvPreferred(
- &_bot->session(),
- url,
- makeOtherContext(false));
- };
- if (const auto info = _bot->botInfo.get()) {
- if (!info->privacyPolicyUrl.isEmpty()) {
- openUrl(info->privacyPolicyUrl);
- return;
- }
- }
- if (!sendCommand()) {
- openUrl(tr::lng_profile_bot_privacy_url(tr::now));
- }
- }
- void WebViewInstance::botClose() {
- crl::on_main(this, [=] { close(); });
- }
- std::shared_ptr<Main::SessionShow> WebViewInstance::uiShow() {
- class Show final : public Main::SessionShow {
- public:
- explicit Show(not_null<WebViewInstance*> that) : _that(that) {
- }
- void showOrHideBoxOrLayer(
- std::variant<
- v::null_t,
- object_ptr<Ui::BoxContent>,
- std::unique_ptr<Ui::LayerWidget>> &&layer,
- Ui::LayerOptions options,
- anim::type animated) const override {
- using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
- using ObjectBox = object_ptr<Ui::BoxContent>;
- const auto panel = _that ? _that->_panel.get() : nullptr;
- if (v::is<UniqueLayer>(layer)) {
- Unexpected("Layers in WebView are not implemented.");
- } else if (auto box = std::get_if<ObjectBox>(&layer)) {
- if (panel) {
- panel->showBox(std::move(*box), options, animated);
- }
- } else if (panel) {
- panel->hideLayer(animated);
- }
- }
- [[nodiscard]] not_null<QWidget*> toastParent() const override {
- const auto panel = _that ? _that->_panel.get() : nullptr;
- Ensures(panel != nullptr);
- return panel->toastParent();
- }
- [[nodiscard]] bool valid() const override {
- return _that && (_that->_panel != nullptr);
- }
- operator bool() const override {
- return valid();
- }
- [[nodiscard]] Main::Session &session() const override {
- Expects(_that.get() != nullptr);
- return *_that->_session;
- }
- private:
- const base::weak_ptr<WebViewInstance> _that;
- };
- return std::make_shared<Show>(this);
- }
- AttachWebView::AttachWebView(not_null<Main::Session*> session)
- : _session(session)
- , _downloads(std::make_unique<Downloads>(session))
- , _refreshTimer([=] { requestBots(); }) {
- _refreshTimer.callEach(kRefreshBotsTimeout);
- }
- AttachWebView::~AttachWebView() {
- closeAll();
- _session->api().request(_popularAppBotsRequestId).cancel();
- }
- void AttachWebView::openByUsername(
- not_null<Window::SessionController*> controller,
- const Api::SendAction &action,
- const QString &botUsername,
- const QString &startCommand,
- bool fullscreen) {
- if (botUsername.isEmpty()
- || (_botUsername == botUsername
- && _startCommand == startCommand
- && _fullScreenRequested == fullscreen)) {
- return;
- }
- cancel();
- _botUsername = botUsername;
- _startCommand = startCommand;
- _fullScreenRequested = fullscreen;
- const auto weak = base::make_weak(controller);
- const auto show = controller->uiShow();
- resolveUsername(show, crl::guard(weak, [=](not_null<PeerData*> peer) {
- _botUsername = QString();
- const auto token = base::take(_startCommand);
- const auto fullscreen = base::take(_fullScreenRequested);
- const auto bot = peer->asUser();
- if (!bot || !bot->isBot()) {
- if (const auto strong = weak.get()) {
- strong->showToast(tr::lng_bot_menu_not_supported(tr::now));
- }
- return;
- }
- open({
- .bot = bot,
- .context = {
- .controller = controller,
- .action = action,
- .fullscreen = fullscreen,
- },
- .button = { .startCommand = token },
- .source = InlineBots::WebViewSourceLinkAttachMenu{},
- });
- }));
- }
- void AttachWebView::close(not_null<WebViewInstance*> instance) {
- const auto i = ranges::find(
- _instances,
- instance.get(),
- &std::unique_ptr<WebViewInstance>::get);
- if (i != end(_instances)) {
- const auto taken = base::take(*i);
- _instances.erase(i);
- }
- }
- void AttachWebView::closeAll() {
- cancel();
- base::take(_instances);
- }
- void AttachWebView::loadPopularAppBots() {
- if (_popularAppBotsLoaded.current() || _popularAppBotsRequestId) {
- return;
- }
- _popularAppBotsRequestId = _session->api().request(
- MTPbots_GetPopularAppBots(
- MTP_string(),
- MTP_int(kPopularAppBotsLimit))
- ).done([=](const MTPbots_PopularAppBots &result) {
- _popularAppBotsRequestId = 0;
- const auto &list = result.data().vusers().v;
- auto parsed = std::vector<not_null<UserData*>>();
- parsed.reserve(list.size());
- for (const auto &user : list) {
- const auto bot = _session->data().processUser(user);
- if (bot->isBot()) {
- parsed.push_back(bot);
- }
- }
- _popularAppBots = std::move(parsed);
- _popularAppBotsLoaded = true;
- }).send();
- }
- auto AttachWebView::popularAppBots() const
- -> const std::vector<not_null<UserData*>> & {
- return _popularAppBots;
- }
- rpl::producer<> AttachWebView::popularAppBotsLoaded() const {
- return _popularAppBotsLoaded.changes() | rpl::to_empty;
- }
- void AttachWebView::cancel() {
- _session->api().request(base::take(_requestId)).cancel();
- _botUsername = QString();
- _startCommand = QString();
- }
- void AttachWebView::requestBots(Fn<void()> callback) {
- if (callback) {
- _botsRequestCallbacks.push_back(std::move(callback));
- }
- if (_botsRequestId) {
- return;
- }
- _botsRequestId = _session->api().request(MTPmessages_GetAttachMenuBots(
- MTP_long(_botsHash)
- )).done([=](const MTPAttachMenuBots &result) {
- _botsRequestId = 0;
- result.match([&](const MTPDattachMenuBotsNotModified &) {
- }, [&](const MTPDattachMenuBots &data) {
- _session->data().processUsers(data.vusers());
- _botsHash = data.vhash().v;
- _attachBots.clear();
- _attachBots.reserve(data.vbots().v.size());
- for (const auto &bot : data.vbots().v) {
- if (auto parsed = ParseAttachBot(_session, bot)) {
- _attachBots.push_back(std::move(*parsed));
- }
- }
- _attachBotsUpdates.fire({});
- });
- for (const auto &callback : base::take(_botsRequestCallbacks)) {
- callback();
- }
- }).fail([=] {
- _botsRequestId = 0;
- for (const auto &callback : base::take(_botsRequestCallbacks)) {
- callback();
- }
- }).send();
- }
- bool AttachWebView::disclaimerAccepted(const AttachWebViewBot &bot) const {
- return _disclaimerAccepted.contains(bot.user);
- }
- bool AttachWebView::showMainMenuNewBadge(
- const AttachWebViewBot &bot) const {
- return bot.inMainMenu
- && bot.disclaimerRequired
- && !disclaimerAccepted(bot);
- }
- void AttachWebView::requestAddToMenu(
- not_null<UserData*> bot,
- Fn<void(AddToMenuResult, PeerTypes supported)> done) {
- auto &process = _addToMenu[bot];
- if (done) {
- process.done.push_back(std::move(done));
- }
- if (process.requestId) {
- return;
- }
- const auto finish = [=](AddToMenuResult result, PeerTypes supported) {
- if (auto process = _addToMenu.take(bot)) {
- for (const auto &done : process->done) {
- done(result, supported);
- }
- }
- };
- if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) {
- finish(AddToMenuResult::Unsupported, {});
- return;
- }
- process.requestId = _session->api().request(
- MTPmessages_GetAttachMenuBot(bot->inputUser)
- ).done([=](const MTPAttachMenuBotsBot &result) {
- _addToMenu[bot].requestId = 0;
- const auto &data = result.data();
- _session->data().processUsers(data.vusers());
- const auto parsed = ParseAttachBot(_session, data.vbot());
- if (!parsed || bot != parsed->user) {
- finish(AddToMenuResult::Unsupported, {});
- return;
- }
- const auto i = ranges::find(
- _attachBots,
- not_null(bot),
- &AttachWebViewBot::user);
- if (i != end(_attachBots)) {
- // Save flags in our list, like 'inactive'.
- *i = *parsed;
- }
- const auto types = parsed->types;
- if (parsed->inactive) {
- confirmAddToMenu(*parsed, [=](bool added) {
- const auto result = added
- ? AddToMenuResult::Added
- : AddToMenuResult::Cancelled;
- finish(result, types);
- });
- } else {
- requestBots();
- finish(AddToMenuResult::AlreadyInMenu, types);
- }
- }).fail([=] {
- finish(AddToMenuResult::Unsupported, {});
- }).send();
- }
- void AttachWebView::removeFromMenu(
- std::shared_ptr<Ui::Show> show,
- not_null<UserData*> bot) {
- toggleInMenu(bot, ToggledState::Removed, [=](bool success) {
- if (success) {
- show->showToast(tr::lng_bot_remove_from_menu_done(tr::now));
- }
- });
- }
- void AttachWebView::resolveUsername(
- std::shared_ptr<Ui::Show> show,
- Fn<void(not_null<PeerData*>)> done) {
- if (const auto peer = _session->data().peerByUsername(_botUsername)) {
- done(peer);
- return;
- }
- _session->api().request(base::take(_requestId)).cancel();
- _requestId = _session->api().request(MTPcontacts_ResolveUsername(
- MTP_flags(0),
- MTP_string(_botUsername),
- MTP_string()
- )).done([=](const MTPcontacts_ResolvedPeer &result) {
- _requestId = 0;
- result.match([&](const MTPDcontacts_resolvedPeer &data) {
- _session->data().processUsers(data.vusers());
- _session->data().processChats(data.vchats());
- if (const auto peerId = peerFromMTP(data.vpeer())) {
- done(_session->data().peer(peerId));
- }
- });
- }).fail([=](const MTP::Error &error) {
- _requestId = 0;
- if (error.code() == 400) {
- show->showToast(
- tr::lng_username_not_found(tr::now, lt_user, _botUsername));
- }
- }).send();
- }
- void AttachWebView::open(WebViewDescriptor &&descriptor) {
- for (const auto &instance : _instances) {
- if (instance->bot() == descriptor.bot
- && instance->source() == descriptor.source) {
- instance->activate();
- return;
- }
- }
- _instances.push_back(
- std::make_unique<WebViewInstance>(std::move(descriptor)));
- _instances.back()->activate();
- }
- void AttachWebView::acceptMainMenuDisclaimer(
- std::shared_ptr<Ui::Show> show,
- not_null<UserData*> bot,
- Fn<void(AddToMenuResult, PeerTypes supported)> done) {
- const auto i = ranges::find(_attachBots, bot, &AttachWebViewBot::user);
- if (i == end(_attachBots)) {
- _attachBotsUpdates.fire({});
- return;
- } else if (i->inactive) {
- requestAddToMenu(bot, std::move(done));
- return;
- } else if (!i->disclaimerRequired || disclaimerAccepted(*i)) {
- done(AddToMenuResult::AlreadyInMenu, i->types);
- return;
- }
- const auto types = i->types;
- show->show(Box(FillDisclaimerBox, crl::guard(this, [=](bool accepted) {
- if (accepted) {
- _disclaimerAccepted.emplace(bot);
- _attachBotsUpdates.fire({});
- done(AddToMenuResult::AlreadyInMenu, types);
- } else {
- done(AddToMenuResult::Cancelled, {});
- }
- })));
- }
- void AttachWebView::confirmAddToMenu(
- AttachWebViewBot bot,
- Fn<void(bool added)> callback) {
- const auto active = Core::App().activeWindow();
- if (!active) {
- if (callback) {
- callback(false);
- }
- return;
- }
- const auto weak = base::make_weak(active);
- active->show(Box([=](not_null<Ui::GenericBox*> box) {
- const auto allowed = std::make_shared<Ui::Checkbox*>();
- const auto disclaimer = !disclaimerAccepted(bot);
- const auto done = [=](Fn<void()> close) {
- const auto state = (disclaimer
- || ((*allowed) && (*allowed)->checked()))
- ? ToggledState::AllowedToWrite
- : ToggledState::Added;
- toggleInMenu(bot.user, state, [=](bool success) {
- if (callback) {
- callback(success);
- }
- if (const auto strong = weak.get()) {
- strong->showToast((bot.inMainMenu
- ? tr::lng_bot_add_to_side_menu_done
- : tr::lng_bot_add_to_menu_done)(tr::now));
- }
- });
- close();
- };
- if (disclaimer) {
- FillDisclaimerBox(box, [=](bool accepted) {
- if (accepted) {
- _disclaimerAccepted.emplace(bot.user);
- _attachBotsUpdates.fire({});
- done([] {});
- } else if (callback) {
- callback(false);
- }
- });
- box->addRow(object_ptr<Ui::FixedHeightWidget>(
- box,
- st::boxRowPadding.left()));
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_bot_will_be_added(
- lt_bot,
- rpl::single(Ui::Text::Bold(bot.name)),
- Ui::Text::WithEntities),
- st::boxLabel));
- } else {
- Ui::ConfirmBox(box, {
- (bot.inMainMenu
- ? tr::lng_bot_add_to_side_menu
- : tr::lng_bot_add_to_menu)(
- tr::now,
- lt_bot,
- Ui::Text::Bold(bot.name),
- Ui::Text::WithEntities),
- done,
- (callback
- ? [=](Fn<void()> close) { callback(false); close(); }
- : Fn<void(Fn<void()>)>()),
- });
- if (bot.requestWriteAccess) {
- (*allowed) = box->addRow(
- object_ptr<Ui::Checkbox>(
- box,
- tr::lng_url_auth_allow_messages(
- tr::now,
- lt_bot,
- Ui::Text::Bold(bot.name),
- Ui::Text::WithEntities),
- true,
- st::urlAuthCheckbox),
- style::margins(
- st::boxRowPadding.left(),
- (disclaimer
- ? st::boxPhotoCaptionSkip
- : st::boxRowPadding.left()),
- st::boxRowPadding.right(),
- st::boxRowPadding.left()));
- (*allowed)->setAllowTextLines();
- }
- }
- }));
- }
- void AttachWebView::toggleInMenu(
- not_null<UserData*> bot,
- ToggledState state,
- Fn<void(bool success)> callback) {
- using Flag = MTPmessages_ToggleBotInAttachMenu::Flag;
- _session->api().request(MTPmessages_ToggleBotInAttachMenu(
- MTP_flags((state == ToggledState::AllowedToWrite)
- ? Flag::f_write_allowed
- : Flag()),
- bot->inputUser,
- MTP_bool(state != ToggledState::Removed)
- )).done([=] {
- _requestId = 0;
- _session->api().request(base::take(_botsRequestId)).cancel();
- requestBots(callback ? [=] { callback(true); } : Fn<void()>());
- }).fail([=] {
- cancel();
- if (callback) {
- callback(false);
- }
- }).send();
- }
- void ChooseAndSendLocation(
- not_null<Window::SessionController*> controller,
- const Ui::LocationPickerConfig &config,
- Api::SendAction action) {
- const auto weak = base::make_weak(controller);
- const auto session = &controller->session();
- if (const auto picker = session->locationPickers().lookup(action)) {
- picker->activate();
- return;
- }
- struct State {
- SendPaymentHelper sendPayment;
- Fn<void(Data::InputVenue, Api::SendAction)> send;
- };
- const auto state = std::make_shared<State>();
- state->send = [=](Data::InputVenue venue, Api::SendAction action) {
- if (const auto strong = weak.get()) {
- const auto withPaymentApproved = [=](int stars) {
- if (const auto onstack = state->send) {
- auto copy = action;
- copy.options.starsApproved = stars;
- onstack(venue, copy);
- }
- };
- const auto checked = state->sendPayment.check(
- strong,
- action.history->peer,
- 1,
- action.options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- }
- state->send = nullptr;
- if (venue.justLocation()) {
- Api::SendLocation(action, venue.lat, venue.lon);
- } else {
- Api::SendVenue(action, venue);
- }
- };
- const auto callback = [=](Data::InputVenue venue) {
- state->send(venue, action);
- };
- const auto picker = Ui::LocationPicker::Show({
- .parent = controller->widget(),
- .config = config,
- .chooseLabel = tr::lng_maps_point_send(),
- .recipient = action.history->peer,
- .session = session,
- .callback = crl::guard(session, callback),
- .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); },
- .storageId = session->local().resolveStorageIdBots(),
- .closeRequests = controller->content()->death(),
- });
- session->locationPickers().emplace(action, picker);
- }
- std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
- not_null<QWidget*> parent,
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer,
- Fn<Api::SendAction()> actionFactory,
- Fn<void(bool)> attach) {
- auto result = std::make_unique<Ui::DropdownMenu>(
- parent,
- st::dropdownMenuWithIcons);
- const auto bots = &peer->session().attachWebView();
- const auto raw = result.get();
- auto minimal = 0;
- if (Data::CanSend(peer, ChatRestriction::SendPhotos, false)) {
- ++minimal;
- raw->addAction(tr::lng_attach_photo_or_video(tr::now), [=] {
- attach(true);
- }, &st::menuIconPhoto);
- }
- const auto fileTypes = ChatRestriction::SendVideos
- | ChatRestriction::SendGifs
- | ChatRestriction::SendStickers
- | ChatRestriction::SendMusic
- | ChatRestriction::SendFiles;
- if (Data::CanSendAnyOf(peer, fileTypes)) {
- ++minimal;
- raw->addAction(tr::lng_attach_document(tr::now), [=] {
- attach(false);
- }, &st::menuIconFile);
- }
- if (peer->canCreatePolls()) {
- ++minimal;
- raw->addAction(tr::lng_polls_create(tr::now), [=] {
- const auto action = actionFactory();
- const auto source = action.options.scheduled
- ? Api::SendType::Scheduled
- : Api::SendType::Normal;
- const auto sendMenuType = (action.replyTo.topicRootId
- || action.history->peer->starsPerMessageChecked())
- ? SendMenu::Type::SilentOnly
- : SendMenu::Type::Scheduled;
- const auto flag = PollData::Flags();
- const auto replyTo = action.replyTo;
- Window::PeerMenuCreatePoll(
- controller,
- peer,
- replyTo,
- flag,
- flag,
- source,
- { sendMenuType });
- }, &st::menuIconCreatePoll);
- }
- const auto session = &controller->session();
- const auto locationType = ChatRestriction::SendOther;
- const auto config = ResolveMapsConfig(session);
- if (Data::CanSendAnyOf(peer, locationType)
- && Ui::LocationPicker::Available(config)) {
- raw->addAction(tr::lng_maps_point(tr::now), [=] {
- ChooseAndSendLocation(controller, config, actionFactory());
- }, &st::menuIconAddress);
- }
- const auto addBots = Data::CanSend(peer, ChatRestriction::SendInline)
- && !peer->starsPerMessageChecked();
- for (const auto &bot : bots->attachBots()) {
- if (!addBots
- || !bot.inAttachMenu
- || !PeerMatchesTypes(peer, bot.user, bot.types)) {
- continue;
- }
- const auto callback = [=] {
- bots->open({
- .bot = bot.user,
- .context = {
- .controller = controller,
- .action = actionFactory(),
- },
- .source = InlineBots::WebViewSourceAttachMenu(),
- });
- };
- auto action = base::make_unique_q<BotAction>(
- raw,
- controller->uiShow(),
- raw->menu()->st(),
- bot,
- callback);
- action->forceShown(
- ) | rpl::start_with_next([=](bool shown) {
- if (shown) {
- raw->setAutoHiding(false);
- } else {
- raw->hideAnimated();
- raw->setAutoHiding(true);
- }
- }, action->lifetime());
- raw->addAction(std::move(action));
- }
- const auto actions = raw->actions().size();
- const auto onclick = ChatHelpers::ShowPanelOnClick();
- if (!actions) {
- return nullptr;
- } else if (actions <= minimal && !onclick) {
- return nullptr;
- }
- return result;
- }
- } // namespace InlineBots
|