Handle bot callback buttons with password.

This commit is contained in:
John Preston 2020-08-25 14:57:17 +04:00
parent 49c230b898
commit fcdc4cd465
10 changed files with 301 additions and 144 deletions

View File

@ -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.";

View File

@ -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<HistoryItem*> item,
int row,
int column,
std::optional<MTPInputCheckPasswordSRP> password = std::nullopt,
Fn<void(const RPCError &)> 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<InformBox>(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<HistoryItem*> item,
int row,
int column) {
SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty());
}
void SendBotCallbackDataWithPassword(
not_null<HistoryItem*> 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<InformBox>(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<rpl::lifetime>();
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<QPointer<PasscodeBox>>();
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<PasscodeBox>(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

View File

@ -16,4 +16,9 @@ void SendBotCallbackData(
int row,
int column);
void SendBotCallbackDataWithPassword(
not_null<HistoryItem*> item,
int row,
int column);
} // namespace Api

View File

@ -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<Ui::GenericBox*> box,
not_null<Main::Session*> 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<Ui::GenericBox*> box,
not_null<Main::Session*> 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<Ui::FlatLabel>(
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<Ui::GenericBox> PrePasswordErrorBox(
const RPCError &error,
not_null<Main::Session*> 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);
}

View File

@ -214,3 +214,8 @@ struct RecoveryEmailValidation {
[[nodiscard]] RecoveryEmailValidation ConfirmRecoveryEmail(
not_null<Main::Session*> session,
const QString &pattern);
[[nodiscard]] object_ptr<Ui::GenericBox> PrePasswordErrorBox(
const RPCError &error,
not_null<Main::Session*> session,
TextWithEntities &&about);

View File

@ -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<Ui::GenericBox*> box, not_null<UserData*> 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<Ui::GenericBox*> box,
not_null<UserData*> 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<Ui::FlatLabel>(
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() {

View File

@ -113,6 +113,13 @@ void activateBotCommand(
column);
} break;
case ButtonType::CallbackWithPassword: {
Api::SendBotCallbackDataWithPassword(
const_cast<HistoryItem*>(msg.get()),
row,
column);
} break;
case ButtonType::Buy: {
Ui::show(Box<InformBox>(tr::lng_payments_not_supported(tr::now)));
} break;

View File

@ -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) {

View File

@ -167,6 +167,7 @@ struct HistoryMessageMarkupButton {
Default,
Url,
Callback,
CallbackWithPassword,
RequestPhone,
RequestLocation,
RequestPoll,

View File

@ -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) {