From fcdc4cd46578bd997299ee6b6c9e17d906b5a085 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Aug 2020 14:57:17 +0400 Subject: [PATCH] Handle bot callback buttons with password. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_bot.cpp | 236 +++++++++++++----- Telegram/SourceFiles/api/api_bot.h | 5 + Telegram/SourceFiles/boxes/passcode_box.cpp | 90 +++++++ Telegram/SourceFiles/boxes/passcode_box.h | 5 + .../boxes/peers/edit_participant_box.cpp | 89 +------ Telegram/SourceFiles/facades.cpp | 7 + .../history/history_item_components.cpp | 7 +- .../history/history_item_components.h | 1 + .../history/view/history_view_message.cpp | 1 + 10 files changed, 301 insertions(+), 144 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 75cbb932c..d477b138b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1853,6 +1853,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_transfer_done_group" = "{user} is now the owner of the group."; "lng_rights_transfer_done_channel" = "{user} is now the owner of the channel."; +"lng_bots_password_confirm_check_about" = "You can finish this action only if you have:"; +"lng_bots_password_confirm_title" = "Two-step verification"; +"lng_bots_password_confirm_description" = "Please enter your password to confirm the action."; + "lng_restricted_send_message" = "The admins of this group restricted you from writing here."; "lng_restricted_send_media" = "The admins of this group restricted you from posting media content here."; "lng_restricted_send_stickers" = "The admins of this group restricted you from posting stickers here."; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index a399084d4..d8824cf20 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_bot.h" #include "apiwrap.h" +#include "core/core_cloud_password.h" #include "api/api_send_progress.h" #include "boxes/confirm_box.h" #include "boxes/share_box.h" +#include "boxes/passcode_box.h" +#include "lang/lang_keys.h" #include "core/click_handler_types.h" #include "data/data_changes.h" #include "data/data_peer.h" @@ -20,10 +23,129 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "main/main_session.h" #include "ui/toast/toast.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" namespace Api { +namespace { void SendBotCallbackData( + not_null item, + int row, + int column, + std::optional password = std::nullopt, + Fn handleError = nullptr) { + if (!IsServerMsgId(item->id)) { + return; + } + const auto history = item->history(); + const auto session = &history->session(); + const auto owner = &history->owner(); + const auto api = &session->api(); + const auto bot = item->getMessageBot(); + const auto fullId = item->fullId(); + const auto getButton = [=] { + return HistoryMessageMarkupButton::Get( + owner, + fullId, + row, + column); + }; + const auto button = getButton(); + if (!button || button->requestId) { + return; + } + + using ButtonType = HistoryMessageMarkupButton::Type; + const auto isGame = (button->type == ButtonType::Game); + + auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0); + QByteArray sendData; + if (isGame) { + flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game; + } else if (button->type == ButtonType::Callback + || button->type == ButtonType::CallbackWithPassword) { + flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data; + sendData = button->data; + } + const auto withPassword = password.has_value(); + if (withPassword) { + flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_password; + } + button->requestId = api->request(MTPmessages_GetBotCallbackAnswer( + MTP_flags(flags), + history->peer->input, + MTP_int(item->id), + MTP_bytes(sendData), + password.value_or(MTP_inputCheckPasswordEmpty()) + )).done([=](const MTPmessages_BotCallbackAnswer &result) { + const auto item = owner->message(fullId); + if (!item) { + return; + } + if (const auto button = getButton()) { + button->requestId = 0; + owner->requestItemRepaint(item); + } + result.match([&](const MTPDmessages_botCallbackAnswer &data) { + if (const auto message = data.vmessage()) { + if (data.is_alert()) { + Ui::show(Box(qs(*message))); + } else { + if (withPassword) { + Ui::hideLayer(); + } + Ui::Toast::Show(qs(*message)); + } + } else if (const auto url = data.vurl()) { + const auto link = qs(*url); + if (!isGame) { + UrlClickHandler::Open(link); + return; + } + const auto scoreLink = AppendShareGameScoreUrl( + session, + link, + item->fullId()); + BotGameUrlClickHandler(bot, scoreLink).onClick({}); + session->sendProgressManager().update( + history, + Api::SendProgressType::PlayGame); + } else if (withPassword) { + Ui::hideLayer(); + } + }); + }).fail([=](const RPCError &error) { + const auto item = owner->message(fullId); + if (!item) { + return; + } + // Show error? + if (const auto button = getButton()) { + button->requestId = 0; + owner->requestItemRepaint(item); + } + if (handleError) { + handleError(error); + } + }).send(); + + session->changes().messageUpdated( + item, + Data::MessageUpdate::Flag::BotCallbackSent + ); +} + +} // namespace + +void SendBotCallbackData( + not_null item, + int row, + int column) { + SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty()); +} + +void SendBotCallbackDataWithPassword( not_null item, int row, int column) { @@ -44,75 +166,63 @@ void SendBotCallbackData( column); }; const auto button = getButton(); - if (!button) { + if (!button || button->requestId) { return; } - - using ButtonType = HistoryMessageMarkupButton::Type; - const auto isGame = (button->type == ButtonType::Game); - - auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0); - QByteArray sendData; - if (isGame) { - flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game; - } else if (button->type == ButtonType::Callback) { - flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data; - sendData = button->data; - } - button->requestId = api->request(MTPmessages_GetBotCallbackAnswer( - MTP_flags(flags), - history->peer->input, - MTP_int(item->id), - MTP_bytes(sendData), - MTPInputCheckPasswordSRP() // #TODO layer118 - )).done([=](const MTPmessages_BotCallbackAnswer &result) { - const auto item = owner->message(fullId); - if (!item) { - return; - } - if (const auto button = getButton()) { - button->requestId = 0; - owner->requestItemRepaint(item); - } - result.match([&](const MTPDmessages_botCallbackAnswer &data) { - if (const auto message = data.vmessage()) { - if (data.is_alert()) { - Ui::show(Box(qs(*message))); - } else { - Ui::Toast::Show(qs(*message)); + api->reloadPasswordState(); + SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty(), [=](const RPCError &error) { + auto box = PrePasswordErrorBox( + error, + session, + tr::lng_bots_password_confirm_check_about( + tr::now, + Ui::Text::WithEntities)); + if (box) { + Ui::show(std::move(box)); + } else { + auto lifetime = std::make_shared(); + button->requestId = -1; + api->passwordState( + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) mutable { + if (lifetime) { + base::take(lifetime)->destroy(); } - } else if (const auto url = data.vurl()) { - const auto link = qs(*url); - if (!isGame) { - UrlClickHandler::Open(link); + if (const auto button = getButton()) { + if (button->requestId == -1) { + button->requestId = 0; + } + } else { return; } - const auto scoreLink = AppendShareGameScoreUrl( - session, - link, - item->fullId()); - BotGameUrlClickHandler(bot, scoreLink).onClick({}); - session->sendProgressManager().update( - history, - Api::SendProgressType::PlayGame); - } - }); - }).fail([=](const RPCError &error) { - const auto item = owner->message(fullId); - if (!item) { - return; + const auto box = std::make_shared>(); + auto fields = PasscodeBox::CloudFields::From(state); + fields.customTitle = tr::lng_bots_password_confirm_title(); + fields.customDescription + = tr::lng_bots_password_confirm_description(tr::now); + fields.customSubmitButton = tr::lng_passcode_submit(); + fields.customCheckCallback = [=]( + const Core::CloudPasswordResult &result) { + if (const auto button = getButton()) { + if (button->requestId) { + return; + } + } else { + return; + } + if (const auto item = owner->message(fullId)) { + SendBotCallbackData(item, row, column, result.result, [=](const RPCError &error) { + if (*box) { + (*box)->handleCustomCheckError(error); + } + }); + } + }; + *box = Ui::show(Box(session, fields)); + }, *lifetime); } - // Show error? - if (const auto button = getButton()) { - button->requestId = 0; - owner->requestItemRepaint(item); - } - }).send(); - - session->changes().messageUpdated( - item, - Data::MessageUpdate::Flag::BotCallbackSent - ); + }); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_bot.h b/Telegram/SourceFiles/api/api_bot.h index 9ce54feff..06a420a41 100644 --- a/Telegram/SourceFiles/api/api_bot.h +++ b/Telegram/SourceFiles/api/api_bot.h @@ -16,4 +16,9 @@ void SendBotCallbackData( int row, int column); +void SendBotCallbackDataWithPassword( + not_null item, + int row, + int column); + } // namespace Api diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index 6cc54aac1..d6688ce7a 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "core/application.h" #include "storage/storage_domain.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" @@ -24,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "passport/passport_encryption.h" #include "passport/passport_panel_edit_contact.h" +#include "settings/settings_privacy_security.h" #include "facades.h" #include "styles/style_layers.h" #include "styles/style_passport.h" @@ -31,6 +34,68 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { +enum class PasswordErrorType { + None, + NoPassword, + Later, +}; + +void SetCloudPassword( + not_null box, + not_null session) { + session->api().passwordState( + ) | rpl::start_with_next([=] { + using namespace Settings; + const auto weak = Ui::MakeWeak(box); + if (CheckEditCloudPassword(session)) { + box->getDelegate()->show( + EditCloudPasswordBox(session)); + } else { + box->getDelegate()->show(CloudPasswordAppOutdatedBox()); + } + if (weak) { + weak->closeBox(); + } + }, box->lifetime()); +} + +void TransferPasswordError( + not_null box, + not_null session, + TextWithEntities &&about, + PasswordErrorType error) { + box->setTitle(tr::lng_rights_transfer_check()); + box->setWidth(st::transferCheckWidth); + + auto text = std::move(about).append('\n').append('\n').append( + tr::lng_rights_transfer_check_password( + tr::now, + Ui::Text::RichLangValue) + ).append('\n').append('\n').append( + tr::lng_rights_transfer_check_session( + tr::now, + Ui::Text::RichLangValue) + ); + if (error == PasswordErrorType::Later) { + text.append('\n').append('\n').append( + tr::lng_rights_transfer_check_later( + tr::now, + Ui::Text::RichLangValue)); + } + box->addRow(object_ptr( + box, + rpl::single(text), + st::boxLabel)); + if (error == PasswordErrorType::Later) { + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); + } else { + box->addButton(tr::lng_rights_transfer_set_password(), [=] { + SetCloudPassword(box, session); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + } +} + } // namespace PasscodeBox::CloudFields PasscodeBox::CloudFields::From( @@ -1139,3 +1204,28 @@ RecoveryEmailValidation ConfirmRecoveryEmail( *weak = box.data(); return { std::move(box), reloads->events(), cancels->events() }; } + +[[nodiscard]] object_ptr PrePasswordErrorBox( + const RPCError &error, + not_null session, + TextWithEntities &&about) { + const auto type = [&] { + const auto &type = error.type(); + if (type == qstr("PASSWORD_MISSING")) { + return PasswordErrorType::NoPassword; + } else if (type.startsWith(qstr("PASSWORD_TOO_FRESH_")) + || type.startsWith(qstr("SESSION_TOO_FRESH_"))) { + return PasswordErrorType::Later; + } + return PasswordErrorType::None; + }(); + if (type == PasswordErrorType::None) { + return nullptr; + } + + return Box( + TransferPasswordError, + session, + std::move(about), + type); +} diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index 54d783793..4275f8fb4 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -214,3 +214,8 @@ struct RecoveryEmailValidation { [[nodiscard]] RecoveryEmailValidation ConfirmRecoveryEmail( not_null session, const QString &pattern); + +[[nodiscard]] object_ptr PrePasswordErrorBox( + const RPCError &error, + not_null session, + TextWithEntities &&about); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 67a19c655..d5fd059d9 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -47,70 +47,6 @@ constexpr auto kSecondsInDay = 24 * 60 * 60; constexpr auto kSecondsInWeek = 7 * kSecondsInDay; constexpr auto kAdminRoleLimit = 16; -enum class PasswordErrorType { - None, - NoPassword, - Later, -}; - -void SetCloudPassword(not_null box, not_null user) { - user->session().api().passwordState( - ) | rpl::start_with_next([=] { - using namespace Settings; - const auto weak = Ui::MakeWeak(box); - if (CheckEditCloudPassword(&user->session())) { - box->getDelegate()->show( - EditCloudPasswordBox(&user->session())); - } else { - box->getDelegate()->show(CloudPasswordAppOutdatedBox()); - } - if (weak) { - weak->closeBox(); - } - }, box->lifetime()); -} - -void TransferPasswordError( - not_null box, - not_null user, - PasswordErrorType error) { - box->setTitle(tr::lng_rights_transfer_check()); - box->setWidth(st::transferCheckWidth); - - auto text = tr::lng_rights_transfer_check_about( - tr::now, - lt_user, - Ui::Text::Bold(user->shortName()), - Ui::Text::WithEntities - ).append('\n').append('\n').append( - tr::lng_rights_transfer_check_password( - tr::now, - Ui::Text::RichLangValue) - ).append('\n').append('\n').append( - tr::lng_rights_transfer_check_session( - tr::now, - Ui::Text::RichLangValue) - ); - if (error == PasswordErrorType::Later) { - text.append('\n').append('\n').append( - tr::lng_rights_transfer_check_later( - tr::now, - Ui::Text::RichLangValue)); - } - box->addRow(object_ptr( - box, - rpl::single(text), - st::boxLabel)); - if (error == PasswordErrorType::Later) { - box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); - } else { - box->addButton(tr::lng_rights_transfer_set_password(), [=] { - SetCloudPassword(box, user); - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - } -} - } // namespace class EditParticipantBox::Inner : public Ui::RpWidget { @@ -520,22 +456,17 @@ void EditAdminBox::transferOwnership() { } bool EditAdminBox::handleTransferPasswordError(const RPCError &error) { - const auto type = [&] { - const auto &type = error.type(); - if (type == qstr("PASSWORD_MISSING")) { - return PasswordErrorType::NoPassword; - } else if (type.startsWith(qstr("PASSWORD_TOO_FRESH_")) - || type.startsWith(qstr("SESSION_TOO_FRESH_"))) { - return PasswordErrorType::Later; - } - return PasswordErrorType::None; - }(); - if (type == PasswordErrorType::None) { - return false; + const auto session = &user()->session(); + auto about = tr::lng_rights_transfer_check_about( + tr::now, + lt_user, + Ui::Text::Bold(user()->shortName()), + Ui::Text::WithEntities); + if (auto box = PrePasswordErrorBox(error, session, std::move(about))) { + getDelegate()->show(std::move(box)); + return true; } - - getDelegate()->show(Box(TransferPasswordError, user(), type)); - return true; + return false; } void EditAdminBox::transferOwnershipChecked() { diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index a2fc7f27b..e5b23fccb 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -113,6 +113,13 @@ void activateBotCommand( column); } break; + case ButtonType::CallbackWithPassword: { + Api::SendBotCallbackDataWithPassword( + const_cast(msg.get()), + row, + column); + } break; + case ButtonType::Buy: { Ui::show(Box(tr::lng_payments_not_supported(tr::now))); } break; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 4b9eb069e..ad78d252a 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -762,7 +762,8 @@ void ReplyKeyboard::Style::paintButton( } } paintButtonIcon(p, rect, outerWidth, button.type); - if (button.type == HistoryMessageMarkupButton::Type::Callback + if (button.type == HistoryMessageMarkupButton::Type::CallbackWithPassword + || button.type == HistoryMessageMarkupButton::Type::Callback || button.type == HistoryMessageMarkupButton::Type::Game) { if (auto data = button.link->getButton()) { if (data->requestId) { @@ -831,7 +832,9 @@ void HistoryMessageReplyMarkup::createFromButtonRows( row.emplace_back(Type::Default, qs(data.vtext())); }, [&](const MTPDkeyboardButtonCallback &data) { row.emplace_back( - Type::Callback, + (data.is_requires_password() + ? Type::CallbackWithPassword + : Type::Callback), qs(data.vtext()), qba(data.vdata())); }, [&](const MTPDkeyboardButtonRequestGeoLocation &data) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index f18026ac5..cf321e28b 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -167,6 +167,7 @@ struct HistoryMessageMarkupButton { Default, Url, Callback, + CallbackWithPassword, RequestPhone, RequestLocation, RequestPoll, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 6d484ca5c..71d30a65e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -123,6 +123,7 @@ int KeyboardStyle::minButtonWidth( case Type::SwitchInlineSame: case Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break; case Type::Callback: + case Type::CallbackWithPassword: case Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break; } if (iconWidth > 0) {