mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-24 09:16:57 +00:00
Handle bot callback buttons with password.
This commit is contained in:
parent
49c230b898
commit
fcdc4cd465
@ -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.";
|
||||
|
@ -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
|
||||
|
@ -16,4 +16,9 @@ void SendBotCallbackData(
|
||||
int row,
|
||||
int column);
|
||||
|
||||
void SendBotCallbackDataWithPassword(
|
||||
not_null<HistoryItem*> item,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
} // namespace Api
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -167,6 +167,7 @@ struct HistoryMessageMarkupButton {
|
||||
Default,
|
||||
Url,
|
||||
Callback,
|
||||
CallbackWithPassword,
|
||||
RequestPhone,
|
||||
RequestLocation,
|
||||
RequestPoll,
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user