From 4fe568cb828714d52a7cdfc0a9f5e249a5531dc5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 1 Mar 2023 17:27:13 +0400 Subject: [PATCH] Implement switch_webview and "web_app_switch_inline_query". --- Telegram/SourceFiles/api/api_bot.cpp | 5 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 3 + .../SourceFiles/history/history_widget.cpp | 50 +-- .../history_view_compose_controls.cpp | 6 +- .../view/history_view_replies_section.cpp | 5 + .../view/history_view_scheduled_section.cpp | 5 + .../inline_bots/bot_attach_web_view.cpp | 292 +++++++++++++----- .../inline_bots/bot_attach_web_view.h | 38 ++- .../inline_bots/inline_results_inner.cpp | 15 +- .../inline_bots/inline_results_inner.h | 9 +- .../inline_bots/inline_results_widget.cpp | 17 +- .../inline_bots/inline_results_widget.h | 1 - Telegram/SourceFiles/mainwidget.cpp | 15 +- .../ui/chat/attach/attach_bot_webview.cpp | 37 +++ .../ui/chat/attach/attach_bot_webview.h | 4 + .../window/window_session_controller.cpp | 55 +++- .../window/window_session_controller.h | 10 + 17 files changed, 415 insertions(+), 152 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index cc5a91ec5..72105bc9e 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -289,10 +289,11 @@ bool SwitchInlineBotButtonReceived( } void ActivateBotCommand(ClickHandlerContext context, int row, int column) { - const auto controller = context.sessionWindow.get(); - if (!controller) { + const auto strong = context.sessionWindow.get(); + if (!strong) { return; } + const auto controller = not_null{ strong }; const auto item = controller->session().data().message(context.itemId); if (!item) { return; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 54fdd1a40..4493cb5e1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -110,6 +110,9 @@ struct EntryState { FilterId filterId = 0; MsgId rootId = 0; MsgId currentReplyToId = 0; + + friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept + = default; }; } // namespace Dialogs diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d1afb5455..9fcc9a720 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -924,8 +924,8 @@ void HistoryWidget::refreshJoinChannelText() { void HistoryWidget::refreshTopBarActiveChat() { const auto state = computeDialogsEntryState(); _topBar->setActiveChat(state, _history->sendActionPainter()); - if (_inlineResults) { - _inlineResults->setCurrentDialogsEntryState(state); + if (state.key) { + controller()->setCurrentDialogsEntryState(state); } } @@ -1477,8 +1477,6 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { sendInlineResult(result); } }); - _inlineResults->setCurrentDialogsEntryState( - computeDialogsEntryState()); _inlineResults->setSendMenuType([=] { return sendMenuType(); }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { @@ -1761,14 +1759,21 @@ void HistoryWidget::setInnerFocus() { } } -bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { +bool HistoryWidget::notify_switchInlineBotButtonReceived( + const QString &query, + UserData *samePeerBot, + MsgId samePeerReplyTo) { if (samePeerBot) { if (_history) { const auto textWithTags = TextWithTags{ '@' + samePeerBot->username() + ' ' + query, TextWithTags::Tags(), }; - MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX }; + MessageCursor cursor = { + int(textWithTags.text.size()), + int(textWithTags.text.size()), + QFIXED_MAX, + }; _history->setLocalDraft(std::make_unique( textWithTags, 0, // replyTo @@ -1782,39 +1787,11 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U const auto to = bot->isBot() ? bot->botInfo->inlineReturnTo : Dialogs::EntryState(); - const auto history = to.key.owningHistory(); - if (!history) { + if (!to.key.owningHistory()) { return false; } bot->botInfo->inlineReturnTo = Dialogs::EntryState(); - using Section = Dialogs::EntryState::Section; - - const auto textWithTags = TextWithTags{ - '@' + bot->username() + ' ' + query, - TextWithTags::Tags(), - }; - MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX }; - auto draft = std::make_unique( - textWithTags, - to.currentReplyToId, - to.rootId, - cursor, - Data::PreviewState::Allowed); - - if (to.section == Section::Scheduled) { - history->setDraft(Data::DraftKey::Scheduled(), std::move(draft)); - controller()->showSection( - std::make_shared(history)); - } else { - history->setLocalDraft(std::move(draft)); - if (to.section == Section::Replies) { - controller()->showRepliesForMessage(history, to.rootId); - } else if (history == _history) { - applyDraft(); - } else { - controller()->showPeerHistory(history->peer); - } - } + controller()->switchInlineQuery(to, bot, query); return true; } return false; @@ -2417,6 +2394,7 @@ void HistoryWidget::refreshAttachBotsMenu() { } _attachBotsMenu = InlineBots::MakeAttachBotsMenu( this, + controller(), _history->peer, [=] { return prepareSendAction({}); }, [=](bool compress) { chooseAttach(compress); }); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 470581c54..faf3626c8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1022,9 +1022,6 @@ void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { _currentDialogsEntryState = state; updateForwarding(); registerDraftSource(); - if (_inlineResults) { - _inlineResults->setCurrentDialogsEntryState(state); - } } PeerData *ComposeControls::sendAsPeer() const { @@ -2405,6 +2402,7 @@ void ComposeControls::updateAttachBotsMenu() { } _attachBotsMenu = InlineBots::MakeAttachBotsMenu( _parent, + _window, _history->peer, _sendActionFactory, [=](bool compress) { _attachRequests.fire_copy(compress); }); @@ -2860,8 +2858,6 @@ void ComposeControls::applyInlineBotQuery( _inlineResults = std::make_unique( _parent, _window); - _inlineResults->setCurrentDialogsEntryState( - _currentDialogsEntryState); _inlineResults->setResultSelectedCallback([=]( InlineBots::ResultSelected result) { if (result.open) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index acee9b334..c41065d04 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1461,6 +1461,7 @@ void RepliesWidget::refreshTopBarActiveChat() { }; _topBar->setActiveChat(state, _sendAction.get()); _composeControls->setCurrentDialogsEntryState(state); + controller()->setCurrentDialogsEntryState(state); } MsgId RepliesWidget::replyToId() const { @@ -1961,6 +1962,10 @@ bool RepliesWidget::showInternal( if (!logMemento->getHighlightId()) { showAtPosition(Data::UnreadMessagePosition); } + if (params.reapplyLocalDraft) { + _composeControls->applyDraft( + ComposeControls::FieldHistoryAction::NewEntry); + } return true; } } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index a5a7e93d4..370642714 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -134,6 +134,7 @@ ScheduledWidget::ScheduledWidget( }; _topBar->setActiveChat(state, nullptr); _composeControls->setCurrentDialogsEntryState(state); + controller->setCurrentDialogsEntryState(state); _topBar->move(0, 0); _topBar->resizeToWidth(width()); @@ -924,6 +925,10 @@ bool ScheduledWidget::showInternal( if (auto logMemento = dynamic_cast(memento.get())) { if (logMemento->getHistory() == history()) { restoreState(logMemento); + if (params.reapplyLocalDraft) { + _composeControls->applyDraft( + ComposeControls::FieldHistoryAction::NewEntry); + } return true; } } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index bdd18e38c..9edb594c9 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/bot_attach_web_view.h" +#include "api/api_common.h" #include "data/data_user.h" #include "data/data_file_origin.h" #include "data/data_document.h" @@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "base/timer_rpl.h" #include "apiwrap.h" +#include "mainwidget.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" @@ -57,18 +59,6 @@ struct ParsedBot { bool inactive = false; }; -[[nodiscard]] bool IsSame( - const std::optional &a, - const Api::SendAction &b) { - // Check fields that are sent to API in bot attach webview requests. - return a.has_value() - && (a->history == b.history) - && (a->replyTo == b.replyTo) - && (a->topicRootId == b.topicRootId) - && (a->options.sendAs == b.options.sendAs) - && (a->options.silent == b.options.silent); -} - [[nodiscard]] DocumentData *ResolveIcon( not_null session, const MTPDattachMenuBot &data) { @@ -132,10 +122,29 @@ struct ParsedBot { return result; } +[[nodiscard]] PeerTypes PeerTypesFromNames( + const std::vector &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; +} + void ShowChooseBox( not_null controller, PeerTypes types, - Fn)> callback) { + Fn)> callback, + rpl::producer titleOverride = nullptr) { const auto weak = std::make_shared>(); auto done = [=](not_null thread) mutable { if (const auto strong = *weak) { @@ -159,7 +168,10 @@ void ShowChooseBox( return (types & PeerType::Group); } }; - auto initBox = [](not_null box) { + auto initBox = [=](not_null box) { + if (titleOverride) { + box->setTitle(std::move(titleOverride)); + } box->addButton(tr::lng_cancel(), [box] { box->closeBox(); }); @@ -407,6 +419,13 @@ PeerTypes ParseChooseTypes(QStringView choose) { return result; } +struct AttachWebView::Context { + base::weak_ptr controller; + Dialogs::EntryState dialogsEntryState; + Api::SendAction action; + bool fromSwitch = false; +}; + AttachWebView::AttachWebView(not_null session) : _session(session) { } @@ -416,6 +435,7 @@ AttachWebView::~AttachWebView() { } void AttachWebView::request( + not_null controller, const Api::SendAction &action, const QString &botUsername, const QString &startCommand) { @@ -423,7 +443,8 @@ void AttachWebView::request( return; } const auto username = _bot ? _bot->username() : _botUsername; - if (IsSame(_action, action) + const auto context = LookupContext(controller, action); + if (IsSame(_context, context) && username.toLower() == botUsername.toLower() && _startCommand == startCommand) { if (_panel) { @@ -433,18 +454,55 @@ void AttachWebView::request( } cancel(); - _action = action; + _context = std::make_unique(context); _botUsername = botUsername; _startCommand = startCommand; resolve(); } +AttachWebView::Context AttachWebView::LookupContext( + not_null controller, + const Api::SendAction &action) { + return { + .controller = controller, + .dialogsEntryState = controller->currentDialogsEntryState(), + .action = action, + }; +} + +bool AttachWebView::IsSame( + const std::unique_ptr &a, + const Context &b) { + // Check fields that are sent to API in bot attach webview requests. + return a + && (a->controller == b.controller) + && (a->dialogsEntryState == b.dialogsEntryState) + && (a->fromSwitch == b.fromSwitch) + && (a->action.history == b.action.history) + && (a->action.replyTo == b.action.replyTo) + && (a->action.topicRootId == b.action.topicRootId) + && (a->action.options.sendAs == b.action.options.sendAs) + && (a->action.options.silent == b.action.options.silent); +} + void AttachWebView::request( - Window::SessionController *controller, + not_null controller, const Api::SendAction &action, not_null bot, const WebViewButton &button) { - if (IsSame(_action, action) && _bot == bot) { + requestWithOptionalConfirm( + bot, + button, + LookupContext(controller, action), + button.fromMenu ? nullptr : controller.get()); +} + +void AttachWebView::requestWithOptionalConfirm( + not_null bot, + const WebViewButton &button, + const Context &context, + Window::SessionController *controllerForConfirm) { + if (IsSame(_context, context) && _bot == bot) { if (_panel) { _panel->requestActivate(); } else if (_requestId) { @@ -454,9 +512,9 @@ void AttachWebView::request( cancel(); _bot = bot; - _action = action; - if (controller) { - confirmOpen(controller, [=] { + _context = std::make_unique(context); + if (controllerForConfirm) { + confirmOpen(controllerForConfirm, [=] { request(button); }); } else { @@ -465,30 +523,31 @@ void AttachWebView::request( } void AttachWebView::request(const WebViewButton &button) { - Expects(_action.has_value() && _bot != nullptr); + Expects(_context != nullptr && _bot != nullptr); _startCommand = button.startCommand; + const auto &action = _context->action; using Flag = MTPmessages_RequestWebView::Flag; const auto flags = Flag::f_theme_params | (button.url.isEmpty() ? Flag(0) : Flag::f_url) | (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param) - | (_action->replyTo ? Flag::f_reply_to_msg_id : Flag(0)) - | (_action->topicRootId ? Flag::f_top_msg_id : Flag(0)) - | (_action->options.sendAs ? Flag::f_send_as : Flag(0)) - | (_action->options.silent ? Flag::f_silent : Flag(0)); + | (action.replyTo ? Flag::f_reply_to_msg_id : Flag(0)) + | (action.topicRootId ? Flag::f_top_msg_id : Flag(0)) + | (action.options.sendAs ? Flag::f_send_as : Flag(0)) + | (action.options.silent ? Flag::f_silent : Flag(0)); _requestId = _session->api().request(MTPmessages_RequestWebView( MTP_flags(flags), - _action->history->peer->input, + action.history->peer->input, _bot->inputUser, MTP_bytes(button.url), MTP_string(_startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop"), - MTP_int(_action->replyTo.bare), - MTP_int(_action->topicRootId.bare), - (_action->options.sendAs - ? _action->options.sendAs->input + MTP_int(action.replyTo.bare), + MTP_int(action.topicRootId.bare), + (action.options.sendAs + ? action.options.sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPWebViewResult &result) { _requestId = 0; @@ -512,7 +571,7 @@ void AttachWebView::cancel() { _session->api().request(base::take(_requestId)).cancel(); _session->api().request(base::take(_prolongId)).cancel(); _panel = nullptr; - _action = std::nullopt; + _context = nullptr; _bot = nullptr; _botUsername = QString(); _startCommand = QString(); @@ -551,21 +610,35 @@ void AttachWebView::requestBots() { } void AttachWebView::requestAddToMenu( - const std::optional &action, + not_null bot, + const QString &startCommand) { + requestAddToMenu(bot, startCommand, nullptr, std::nullopt, PeerTypes()); +} + +void AttachWebView::requestAddToMenu( not_null bot, const QString &startCommand, Window::SessionController *controller, + std::optional action, PeerTypes chooseTypes) { + Expects(controller != nullptr || _context != nullptr); + if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { Ui::ShowMultilineToast({ .text = { tr::lng_bot_menu_not_supported(tr::now) }, }); return; } + const auto wasController = (controller != nullptr); _addToMenuChooseController = base::make_weak(controller); _addToMenuStartCommand = startCommand; _addToMenuChooseTypes = chooseTypes; - _addToMenuAction = action; + if (!controller) { + _addToMenuContext = base::take(_context); + } else if (action) { + _addToMenuContext = std::make_unique( + LookupContext(controller, *action)); + } if (_addToMenuId) { if (_addToMenuBot == bot) { return; @@ -578,32 +651,35 @@ void AttachWebView::requestAddToMenu( )).done([=](const MTPAttachMenuBotsBot &result) { _addToMenuId = 0; const auto bot = base::take(_addToMenuBot); - const auto contextAction = base::take(_addToMenuAction); + const auto context = std::shared_ptr(base::take(_addToMenuContext)); const auto chooseTypes = base::take(_addToMenuChooseTypes); const auto startCommand = base::take(_addToMenuStartCommand); const auto chooseController = base::take(_addToMenuChooseController); const auto open = [=](PeerTypes types) { - if (const auto useTypes = chooseTypes & types) { - if (const auto strong = chooseController.get()) { - const auto done = [=](not_null thread) { - strong->showThread(thread); - request( - nullptr, - Api::SendAction(thread), - bot, - { .startCommand = startCommand }); - }; - ShowChooseBox(strong, useTypes, done); + const auto strong = chooseController.get(); + if (!strong) { + if (wasController) { + // Just ignore the click if controller was destroyed. + return true; } + } else if (const auto useTypes = chooseTypes & types) { + const auto done = [=](not_null thread) { + strong->showThread(thread); + requestWithOptionalConfirm( + bot, + { .startCommand = startCommand }, + LookupContext(strong, Api::SendAction(thread))); + }; + ShowChooseBox(strong, useTypes, done); return true; - } else if (!contextAction) { + } + if (!context) { return false; } - request( - nullptr, - *contextAction, + requestWithOptionalConfirm( bot, - { .startCommand = startCommand }); + { .startCommand = startCommand }, + *context); return true; }; result.match([&](const MTPDattachMenuBotsBot &data) { @@ -630,7 +706,7 @@ void AttachWebView::requestAddToMenu( }).fail([=] { _addToMenuId = 0; _addToMenuBot = nullptr; - _addToMenuAction = std::nullopt; + _addToMenuContext = nullptr; _addToMenuStartCommand = QString(); Ui::ShowMultilineToast({ .text = { tr::lng_bot_menu_not_supported(tr::now) }, @@ -648,6 +724,9 @@ void AttachWebView::removeFromMenu(not_null bot) { void AttachWebView::resolve() { resolveUsername(_botUsername, [=](not_null bot) { + if (!_context) { + return; + } _bot = bot->asUser(); if (!_bot) { Ui::ShowMultilineToast({ @@ -655,7 +734,7 @@ void AttachWebView::resolve() { }); return; } - requestAddToMenu(_action, _bot, _startCommand); + requestAddToMenu(_bot, _startCommand); }); } @@ -696,7 +775,10 @@ void AttachWebView::requestSimple( const WebViewButton &button) { cancel(); _bot = bot; - _action = Api::SendAction(bot->owner().history(bot)); + _context = std::make_unique(LookupContext( + controller, + Api::SendAction(bot->owner().history(bot)))); + _context->fromSwitch = button.fromSwitch; confirmOpen(controller, [=] { requestSimple(button); }); @@ -705,7 +787,8 @@ void AttachWebView::requestSimple( void AttachWebView::requestSimple(const WebViewButton &button) { using Flag = MTPmessages_RequestSimpleWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestSimpleWebView( - MTP_flags(Flag::f_theme_params), + MTP_flags(Flag::f_theme_params + | (button.fromSwitch ? Flag::f_from_switch_webview : Flag())), _bot->inputUser, MTP_bytes(button.url), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), @@ -726,29 +809,32 @@ void AttachWebView::requestMenu( not_null bot) { cancel(); _bot = bot; - _action = Api::SendAction(bot->owner().history(bot)); + _context = std::make_unique(LookupContext( + controller, + Api::SendAction(bot->owner().history(bot)))); const auto url = bot->botInfo->botMenuButtonUrl; const auto text = bot->botInfo->botMenuButtonText; confirmOpen(controller, [=] { + const auto &action = _context->action; using Flag = MTPmessages_RequestWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestWebView( MTP_flags(Flag::f_theme_params | Flag::f_url | Flag::f_from_bot_menu - | (_action->replyTo? Flag::f_reply_to_msg_id : Flag(0)) - | (_action->topicRootId ? Flag::f_top_msg_id : Flag(0)) - | (_action->options.sendAs ? Flag::f_send_as : Flag(0)) - | (_action->options.silent ? Flag::f_silent : Flag(0))), - _action->history->peer->input, + | (action.replyTo? Flag::f_reply_to_msg_id : Flag(0)) + | (action.topicRootId ? Flag::f_top_msg_id : 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_string(url), MTPstring(), // start_param MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop"), - MTP_int(_action->replyTo.bare), - MTP_int(_action->topicRootId.bare), - (_action->options.sendAs - ? _action->options.sendAs->input + MTP_int(action.replyTo.bare), + MTP_int(action.topicRootId.bare), + (action.options.sendAs + ? action.options.sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPWebViewResult &result) { _requestId = 0; @@ -801,13 +887,16 @@ void AttachWebView::show( const QString &url, const QString &buttonText, bool allowClipboardRead) { - Expects(_bot != nullptr && _action.has_value()); + Expects(_bot != nullptr && _context != nullptr); const auto close = crl::guard(this, [=] { crl::on_main(this, [=] { cancel(); }); }); const auto sendData = crl::guard(this, [=](QByteArray data) { - if (!_action || _action->history->peer != _bot || queryId) { + if (!_context + || _context->fromSwitch + || _context->action.history->peer != _bot + || queryId) { return; } const auto randomId = base::RandomValue(); @@ -821,6 +910,43 @@ void AttachWebView::show( }).send(); crl::on_main(this, [=] { cancel(); }); }); + const auto switchInlineQuery = crl::guard(this, [=]( + std::vector typeNames, + QString query) { + const auto controller = _context + ? _context->controller.get() + : nullptr; + const auto types = PeerTypesFromNames(typeNames); + 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 botAndQuery = '@' + + _bot->username() + + ' ' + + query; + const auto done = [=](not_null thread) { + return controller->content()->inlineSwitchChosen( + thread, + botAndQuery); + }; + ShowChooseBox( + controller, + types, + done, + tr::lng_inline_switch_choose()); + } + crl::on_main(this, [=] { cancel(); }); + }); const auto handleLocalUri = [close](QString uri) { const auto local = Core::TryConvertUrlToLocal(uri); if (uri == local || Core::InternalPassportLink(local)) { @@ -868,7 +994,8 @@ void AttachWebView::show( const auto hasSettings = (attached != end(_attachBots)) && !attached->inactive && attached->hasSettings; - const auto hasOpenBot = !_action || (_bot != _action->history->peer); + const auto hasOpenBot = !_context + || (_bot != _context->action.history->peer); const auto hasRemoveFromMenu = (attached != end(_attachBots)) && !attached->inactive; const auto buttons = (hasSettings ? Button::Settings : Button::None) @@ -919,6 +1046,7 @@ void AttachWebView::show( .handleLocalUri = handleLocalUri, .handleInvoice = handleInvoice, .sendData = sendData, + .switchInlineQuery = switchInlineQuery, .close = close, .phone = _session->user()->phone(), .menuButtons = buttons, @@ -931,7 +1059,11 @@ void AttachWebView::show( } void AttachWebView::started(uint64 queryId) { - Expects(_action.has_value() && _bot != nullptr); + Expects(_bot != nullptr && _context != nullptr); + + if (_context->fromSwitch) { + return; + } _session->data().webViewResultSent( ) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) { @@ -940,6 +1072,7 @@ void AttachWebView::started(uint64 queryId) { cancel(); }, _panel->lifetime()); + const auto action = _context->action; base::timer_each( kProlongTimeout ) | rpl::start_with_next([=] { @@ -947,17 +1080,17 @@ void AttachWebView::started(uint64 queryId) { _session->api().request(base::take(_prolongId)).cancel(); _prolongId = _session->api().request(MTPmessages_ProlongWebView( MTP_flags(Flag(0) - | (_action->replyTo ? Flag::f_reply_to_msg_id : Flag(0)) - | (_action->topicRootId ? Flag::f_top_msg_id : Flag(0)) - | (_action->options.sendAs ? Flag::f_send_as : Flag(0)) - | (_action->options.silent ? Flag::f_silent : Flag(0))), - _action->history->peer->input, + | (action.replyTo ? Flag::f_reply_to_msg_id : Flag(0)) + | (action.topicRootId ? Flag::f_top_msg_id : 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), - MTP_int(_action->replyTo.bare), - MTP_int(_action->topicRootId.bare), - (_action->options.sendAs - ? _action->options.sendAs->input + MTP_int(action.replyTo.bare), + MTP_int(action.topicRootId.bare), + (action.options.sendAs + ? action.options.sendAs->input : MTP_inputPeerEmpty()) )).done([=] { _prolongId = 0; @@ -1041,6 +1174,7 @@ void AttachWebView::toggleInMenu( std::unique_ptr MakeAttachBotsMenu( not_null parent, + not_null controller, not_null peer, Fn actionFactory, Fn attach) { @@ -1076,7 +1210,7 @@ std::unique_ptr MakeAttachBotsMenu( } const auto callback = [=] { bots->request( - nullptr, + controller, actionFactory(), bot.user, { .fromMenu = true }); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index c22714930..8f960b492 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -7,11 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "api/api_common.h" #include "mtproto/sender.h" #include "base/weak_ptr.h" #include "base/flags.h" +namespace Api { +struct SendAction; +} // namespace Api + namespace Ui { class GenericBox; class DropdownMenu; @@ -71,13 +74,15 @@ public: QString startCommand; QByteArray url; bool fromMenu = false; + bool fromSwitch = false; }; void request( + not_null controller, const Api::SendAction &action, const QString &botUsername, const QString &startCommand); void request( - Window::SessionController *controller, + not_null controller, const Api::SendAction &action, not_null bot, const WebViewButton &button); @@ -100,16 +105,34 @@ public: } void requestAddToMenu( - const std::optional &action, + not_null bot, + const QString &startCommand); + void requestAddToMenu( not_null bot, const QString &startCommand, - Window::SessionController *controller = nullptr, - PeerTypes chooseTypes = {}); + Window::SessionController *controller, + std::optional action, + PeerTypes chooseTypes); void removeFromMenu(not_null bot); static void ClearAll(); private: + struct Context; + + [[nodiscard]] static Context LookupContext( + not_null controller, + const Api::SendAction &action); + [[nodiscard]] static bool IsSame( + const std::unique_ptr &a, + const Context &b); + + void requestWithOptionalConfirm( + not_null bot, + const WebViewButton &button, + const Context &context, + Window::SessionController *controllerForConfirm = nullptr); + void resolve(); void request(const WebViewButton &button); void requestSimple(const WebViewButton &button); @@ -143,7 +166,7 @@ private: const not_null _session; - std::optional _action; + std::unique_ptr _context; UserData *_bot = nullptr; QString _botUsername; QString _startCommand; @@ -155,7 +178,7 @@ private: uint64 _botsHash = 0; mtpRequestId _botsRequestId = 0; - std::optional _addToMenuAction; + std::unique_ptr _addToMenuContext; UserData *_addToMenuBot = nullptr; mtpRequestId _addToMenuId = 0; QString _addToMenuStartCommand; @@ -171,6 +194,7 @@ private: [[nodiscard]] std::unique_ptr MakeAttachBotsMenu( not_null parent, + not_null controller, not_null peer, Fn actionFactory, Fn attach); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 419779480..90af03e55 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_chat_participant_status.h" #include "data/data_session.h" +#include "inline_bots/bot_attach_web_view.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "lang/lang_keys.h" @@ -453,6 +454,7 @@ void Inner::refreshSwitchPmButton(const CacheEntry *entry) { if (!entry || entry->switchPmText.isEmpty()) { _switchPmButton.destroy(); _switchPmStartToken.clear(); + _switchPmUrl = QByteArray(); } else { if (!_switchPmButton) { _switchPmButton.create(this, nullptr, st::switchPmButton); @@ -462,6 +464,7 @@ void Inner::refreshSwitchPmButton(const CacheEntry *entry) { } _switchPmButton->setText(rpl::single(entry->switchPmText)); _switchPmStartToken = entry->switchPmStartToken; + _switchPmUrl = entry->switchPmUrl; const auto buttonTop = st::stickerPanPadding; _switchPmButton->move(st::inlineResultsLeft - st::roundRadiusSmall, buttonTop); if (isRestrictedView()) { @@ -667,9 +670,17 @@ void Inner::repaintItems(crl::time now) { } void Inner::switchPm() { - if (_inlineBot && _inlineBot->isBot()) { + if (!_inlineBot || !_inlineBot->isBot()) { + return; + } else if (!_switchPmUrl.isEmpty()) { + _inlineBot->session().attachWebView().requestSimple( + _controller, + _inlineBot, + { .url = _switchPmUrl, .fromSwitch = true }); + } else { _inlineBot->botInfo->startToken = _switchPmStartToken; - _inlineBot->botInfo->inlineReturnTo = _currentDialogsEntryState; + _inlineBot->botInfo->inlineReturnTo + = _controller->currentDialogsEntryState(); _controller->showPeerHistory( _inlineBot, Window::SectionShow::Way::ClearStack, diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h index efeed346e..5aa22578a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -54,7 +54,9 @@ using Results = std::vector>; struct CacheEntry { QString nextOffset; - QString switchPmText, switchPmStartToken; + QString switchPmText; + QString switchPmStartToken; + QByteArray switchPmUrl; Results results; }; @@ -87,9 +89,6 @@ public: void setResultSelectedCallback(Fn callback) { _resultSelectedCallback = std::move(callback); } - void setCurrentDialogsEntryState(Dialogs::EntryState state) { - _currentDialogsEntryState = state; - } void setSendMenuType(Fn &&callback); // Ui::AbstractTooltipShower interface. @@ -160,7 +159,7 @@ private: object_ptr _switchPmButton = { nullptr }; QString _switchPmStartToken; - Dialogs::EntryState _currentDialogsEntryState; + QByteArray _switchPmUrl; object_ptr _restrictedLabel = { nullptr }; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index ea3d94152..3808ed989 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -263,10 +263,6 @@ void Widget::setSendMenuType(Fn &&callback) { _inner->setSendMenuType(std::move(callback)); } -void Widget::setCurrentDialogsEntryState(Dialogs::EntryState state) { - _inner->setCurrentDialogsEntryState(state); -} - void Widget::hideAnimated() { if (isHidden()) return; if (_hiding) return; @@ -384,13 +380,16 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) { auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset().value_or_empty()); if (const auto switchPm = d.vswitch_pm()) { - switchPm->match([&](const MTPDinlineBotSwitchPM &data) { - entry->switchPmText = qs(data.vtext()); - entry->switchPmStartToken = qs(data.vstart_param()); - }); + entry->switchPmText = qs(switchPm->data().vtext()); + entry->switchPmStartToken = qs(switchPm->data().vstart_param()); + entry->switchPmUrl = QByteArray(); + } else if (const auto switchWebView = d.vswitch_webview()) { + entry->switchPmText = qs(switchWebView->data().vtext()); + entry->switchPmStartToken = QString(); + entry->switchPmUrl = switchWebView->data().vurl().v; } - if (auto count = v.size()) { + if (const auto count = v.size()) { entry->results.reserve(entry->results.size() + count); } auto added = 0; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index 7a26bcaf8..00b39e0d3 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -76,7 +76,6 @@ public: void setResultSelectedCallback(Fn callback); void setSendMenuType(Fn &&callback); - void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] rpl::producer requesting() const { return _requesting.events(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index aebf652fe..e82289ce3 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1341,7 +1341,9 @@ void MainWidget::showHistory( _controller->window().activate(); } - if (!(_history->peer() && _history->peer()->id == peerId) + const auto alreadyThatPeer = _history->peer() + && (_history->peer()->id == peerId); + if (!alreadyThatPeer && preventsCloseSection( [=] { showHistory(peerId, params, showAtMsgId); }, params)) { @@ -1424,17 +1426,24 @@ void MainWidget::showHistory( return false; }; - auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams(); + auto animationParams = animatedShow() + ? prepareHistoryAnimation(peerId) + : Window::SectionSlideParams(); if (!back && (way != Way::ClearStack)) { // This may modify the current section, for example remove its contents. saveSectionInStack(); } - if (_history->peer() && _history->peer()->id != peerId && way != Way::Forward) { + if (_history->peer() + && _history->peer()->id != peerId + && way != Way::Forward) { clearBotStartToken(_history->peer()); } _history->showHistory(peerId, showAtMsgId); + if (alreadyThatPeer && params.reapplyLocalDraft) { + _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); + } auto noPeer = !_history->peer(); auto onlyDialogs = noPeer && isOneColumn(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 36dab0d4a..7dff179cc 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -318,6 +318,7 @@ Panel::Panel( Fn handleLocalUri, Fn handleInvoice, Fn sendData, + Fn, QString)> switchInlineQuery, Fn close, QString phone, MenuButtons menuButtons, @@ -328,6 +329,7 @@ Panel::Panel( , _handleLocalUri(std::move(handleLocalUri)) , _handleInvoice(std::move(handleInvoice)) , _sendData(std::move(sendData)) +, _switchInlineQuery(std::move(switchInlineQuery)) , _close(std::move(close)) , _phone(phone) , _menuButtons(menuButtons) @@ -625,6 +627,8 @@ bool Panel::createWebview() { _close(); } else if (command == "web_app_data_send") { sendDataMessage(arguments); + } else if (command == "web_app_switch_inline_query") { + switchInlineQueryMessage(arguments); } else if (command == "web_app_setup_main_button") { processMainButtonMessage(arguments); } else if (command == "web_app_setup_back_button") { @@ -702,6 +706,38 @@ void Panel::sendDataMessage(const QJsonObject &args) { _sendData(data.toUtf8()); } +void Panel::switchInlineQueryMessage(const QJsonObject &args) { + if (args.isEmpty()) { + _close(); + return; + } + const auto query = args["query"].toString(); + if (query.isEmpty()) { + LOG(("BotWebView Error: Bad 'query' in switchInlineQueryMessage.")); + _close(); + return; + } + const auto valid = base::flat_set{ + u"users"_q, + u"bots"_q, + u"groups"_q, + u"channels"_q, + }; + auto types = std::vector(); + for (const auto &value : args["chat_types"].toArray()) { + const auto type = value.toString(); + if (valid.contains(type)) { + types.push_back(type); + } else { + LOG(("BotWebView Error: " + "Bad chat type in switchInlineQueryMessage: %1.").arg(type)); + types.clear(); + break; + } + } + _switchInlineQuery(types, query); +} + void Panel::openTgLink(const QJsonObject &args) { if (args.isEmpty()) { _close(); @@ -1129,6 +1165,7 @@ std::unique_ptr Show(Args &&args) { std::move(args.handleLocalUri), std::move(args.handleInvoice), std::move(args.sendData), + std::move(args.switchInlineQuery), std::move(args.close), args.phone, args.menuButtons, diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index fe51fd319..a3f48b2d2 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -48,6 +48,7 @@ public: Fn handleLocalUri, Fn handleInvoice, Fn sendData, + Fn, QString)> switchInlineQuery, Fn close, QString phone, MenuButtons menuButtons, @@ -88,6 +89,7 @@ private: void hideWebviewProgress(); void setTitle(rpl::producer title); void sendDataMessage(const QJsonObject &args); + void switchInlineQueryMessage(const QJsonObject &args); void processMainButtonMessage(const QJsonObject &args); void processBackButtonMessage(const QJsonObject &args); void openTgLink(const QJsonObject &args); @@ -116,6 +118,7 @@ private: Fn _handleLocalUri; Fn _handleInvoice; Fn _sendData; + Fn, QString)> _switchInlineQuery; Fn _close; QString _phone; bool _closeNeedConfirmation = false; @@ -147,6 +150,7 @@ struct Args { Fn handleLocalUri; Fn handleInvoice; Fn sendData; + Fn, QString)> switchInlineQuery; Fn close; QString phone; MenuButtons menuButtons; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 81a369019..4584cb888 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -434,6 +434,7 @@ void SessionNavigation::showPeerByLinkResolved( const auto history = peer->owner().history(peer); showPeerHistory(history, params, msgId); peer->session().attachWebView().request( + parentController(), Api::SendAction(history), attachBotUsername, info.attachBotToggleCommand.value_or(QString())); @@ -448,13 +449,13 @@ void SessionNavigation::showPeerByLinkResolved( ? contextPeer->asUser() : nullptr; bot->session().attachWebView().requestAddToMenu( + bot, + *info.attachBotToggleCommand, + parentController(), (contextUser ? Api::SendAction( contextUser->owner().history(contextUser)) : std::optional()), - bot, - *info.attachBotToggleCommand, - parentController(), info.attachBotChooseTypes); } else { crl::on_main(this, [=] { @@ -1150,6 +1151,54 @@ bool SessionController::jumpToChatListEntry(Dialogs::RowDescriptor row) { return false; } +void SessionController::setCurrentDialogsEntryState( + Dialogs::EntryState state) { + _currentDialogsEntryState = state; +} + +Dialogs::EntryState SessionController::currentDialogsEntryState() const { + return _currentDialogsEntryState; +} + +void SessionController::switchInlineQuery( + Dialogs::EntryState to, + not_null bot, + const QString &query) { + Expects(to.key.owningHistory() != nullptr); + + using Section = Dialogs::EntryState::Section; + + const auto history = to.key.owningHistory(); + const auto textWithTags = TextWithTags{ + '@' + bot->username() + ' ' + query, + TextWithTags::Tags(), + }; + MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX }; + auto draft = std::make_unique( + textWithTags, + to.currentReplyToId, + to.rootId, + cursor, + Data::PreviewState::Allowed); + + auto params = Window::SectionShow(); + params.reapplyLocalDraft = true; + if (to.section == Section::Scheduled) { + history->setDraft(Data::DraftKey::Scheduled(), std::move(draft)); + showSection( + std::make_shared(history), + params); + } else { + history->setLocalDraft(std::move(draft)); + if (to.section == Section::Replies) { + const auto commentId = MsgId(); + showRepliesForMessage(history, to.rootId, commentId, params); + } else { + showPeerHistory(history->peer, params); + } + } +} + Dialogs::RowDescriptor SessionController::resolveChatNext( Dialogs::RowDescriptor from) const { return content()->resolveChatNext(from); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index dad287fd5..d83adab46 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -171,6 +171,7 @@ struct SectionShow { anim::activation activation = anim::activation::normal; bool thirdColumn = false; bool childColumn = false; + bool reapplyLocalDraft = false; Origin origin; }; @@ -373,6 +374,13 @@ public: rpl::producer activeChatValue() const; bool jumpToChatListEntry(Dialogs::RowDescriptor row); + void setCurrentDialogsEntryState(Dialogs::EntryState state); + [[nodiscard]] Dialogs::EntryState currentDialogsEntryState() const; + void switchInlineQuery( + Dialogs::EntryState to, + not_null bot, + const QString &query); + [[nodiscard]] Dialogs::RowDescriptor resolveChatNext( Dialogs::RowDescriptor from = {}) const; [[nodiscard]] Dialogs::RowDescriptor resolveChatPrevious( @@ -635,6 +643,8 @@ private: int _chatEntryHistoryPosition = -1; bool _filtersActivated = false; + Dialogs::EntryState _currentDialogsEntryState; + base::Timer _invitePeekTimer; rpl::variable _activeChatsFilter;