2022-03-29 13:00:10 +04:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
|
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
#include "inline_bots/bot_attach_web_view.h"
|
|
|
|
|
|
|
|
#include "data/data_user.h"
|
|
|
|
#include "data/data_session.h"
|
|
|
|
#include "main/main_session.h"
|
|
|
|
#include "main/main_domain.h"
|
|
|
|
#include "storage/storage_domain.h"
|
2022-03-29 14:34:57 +04:00
|
|
|
#include "info/profile/info_profile_values.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
#include "ui/boxes/confirm_box.h"
|
2022-03-29 16:13:54 +04:00
|
|
|
#include "ui/widgets/dropdown_menu.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
#include "ui/toasts/common_toasts.h"
|
|
|
|
#include "ui/chat/attach/attach_bot_webview.h"
|
|
|
|
#include "window/themes/window_theme.h"
|
|
|
|
#include "window/window_controller.h"
|
2022-03-29 16:13:54 +04:00
|
|
|
#include "window/window_session_controller.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
#include "core/application.h"
|
2022-03-29 16:13:54 +04:00
|
|
|
#include "history/history.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "base/random.h"
|
2022-03-29 14:34:57 +04:00
|
|
|
#include "base/timer_rpl.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
#include "apiwrap.h"
|
2022-03-29 16:13:54 +04:00
|
|
|
#include "styles/style_menu_icons.h"
|
2022-03-29 13:00:10 +04:00
|
|
|
|
|
|
|
namespace InlineBots {
|
|
|
|
namespace {
|
|
|
|
|
2022-03-29 14:34:57 +04:00
|
|
|
constexpr auto kProlongTimeout = 60 * crl::time(1000);
|
|
|
|
|
2022-03-29 13:00:10 +04:00
|
|
|
[[nodiscard]] UserData *ParseAttachBot(
|
|
|
|
not_null<Main::Session*> session,
|
|
|
|
const MTPAttachMenuBot &bot) {
|
|
|
|
return bot.match([&](const MTPDattachMenuBot &data) {
|
|
|
|
const auto user = session->data().userLoaded(UserId(data.vbot_id()));
|
|
|
|
return (user && user->isBot() && user->botInfo->supportsAttachMenu)
|
|
|
|
? user
|
|
|
|
: nullptr;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-29 14:34:57 +04:00
|
|
|
[[nodiscard]] base::flat_set<not_null<AttachWebView*>> &ActiveWebViews() {
|
|
|
|
static auto result = base::flat_set<not_null<AttachWebView*>>();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-03-29 13:00:10 +04:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
|
|
|
: _session(session) {
|
|
|
|
}
|
|
|
|
|
2022-03-29 14:34:57 +04:00
|
|
|
AttachWebView::~AttachWebView() {
|
|
|
|
ActiveWebViews().remove(this);
|
|
|
|
}
|
2022-03-29 13:00:10 +04:00
|
|
|
|
|
|
|
void AttachWebView::request(
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
const QString &botUsername) {
|
|
|
|
const auto username = _bot ? _bot->username : _botUsername;
|
|
|
|
if (_peer == peer && username.toLower() == botUsername.toLower()) {
|
|
|
|
if (_panel) {
|
|
|
|
_panel->requestActivate();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cancel();
|
|
|
|
|
|
|
|
_peer = peer;
|
|
|
|
_botUsername = botUsername;
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::request(
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
not_null<UserData*> bot,
|
|
|
|
const WebViewButton &button) {
|
|
|
|
if (_peer == peer && _bot == bot) {
|
|
|
|
if (_panel) {
|
|
|
|
_panel->requestActivate();
|
|
|
|
} else if (_requestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cancel();
|
|
|
|
|
|
|
|
_bot = bot;
|
|
|
|
_peer = peer;
|
|
|
|
request(button);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::request(const WebViewButton &button) {
|
|
|
|
Expects(_peer != nullptr && _bot != nullptr);
|
|
|
|
|
|
|
|
using Flag = MTPmessages_RequestWebView::Flag;
|
|
|
|
const auto flags = Flag::f_theme_params
|
|
|
|
| (button.url.isEmpty() ? Flag(0) : Flag::f_url);
|
|
|
|
_requestId = _session->api().request(MTPmessages_RequestWebView(
|
|
|
|
MTP_flags(flags),
|
|
|
|
_peer->input,
|
|
|
|
_bot->inputUser,
|
|
|
|
MTP_bytes(button.url),
|
|
|
|
MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams())),
|
|
|
|
MTPint() // reply_to_msg_id
|
|
|
|
)).done([=](const MTPWebViewResult &result) {
|
|
|
|
_requestId = 0;
|
|
|
|
result.match([&](const MTPDwebViewResultUrl &data) {
|
|
|
|
show(data.vquery_id().v, qs(data.vurl()), button.text);
|
|
|
|
}, [&](const MTPDwebViewResultConfirmationRequired &data) {
|
|
|
|
_session->data().processUsers(data.vusers());
|
|
|
|
const auto &received = data.vbot();
|
|
|
|
if (const auto bot = ParseAttachBot(_session, received)) {
|
|
|
|
if (_bot != bot) {
|
|
|
|
cancel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
requestAddToMenu([=] {
|
|
|
|
request(button);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cancel();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}).fail([=](const MTP::Error &error) {
|
|
|
|
_requestId = 0;
|
|
|
|
int a = error.code();
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::cancel() {
|
2022-03-29 14:34:57 +04:00
|
|
|
ActiveWebViews().remove(this);
|
2022-03-29 13:00:10 +04:00
|
|
|
_session->api().request(base::take(_requestId)).cancel();
|
2022-03-29 14:34:57 +04:00
|
|
|
_session->api().request(base::take(_prolongId)).cancel();
|
2022-03-29 13:00:10 +04:00
|
|
|
_panel = nullptr;
|
|
|
|
_peer = _bot = nullptr;
|
|
|
|
_botUsername = QString();
|
|
|
|
}
|
|
|
|
|
2022-03-29 16:13:54 +04:00
|
|
|
void AttachWebView::requestBots() {
|
|
|
|
if (_botsRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_botsRequestId = _session->api().request(MTPmessages_GetAttachMenuBots(
|
|
|
|
MTP_long(_botsHash)
|
|
|
|
)).done([=](const MTPAttachMenuBots &result) {
|
|
|
|
_botsRequestId = 0;
|
|
|
|
result.match([&](const MTPDattachMenuBotsNotModified &) {
|
|
|
|
}, [&](const MTPDattachMenuBots &data) {
|
|
|
|
_session->data().processUsers(data.vusers());
|
|
|
|
_botsHash = data.vhash().v;
|
|
|
|
_attachBots.clear();
|
|
|
|
_attachBots.reserve(data.vbots().v.size());
|
|
|
|
for (const auto &bot : data.vbots().v) {
|
|
|
|
bot.match([&](const MTPDattachMenuBot &data) {
|
|
|
|
if (data.is_inactive()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_attachBots.push_back({
|
|
|
|
.user = _session->data().user(data.vbot_id()),
|
|
|
|
.name = qs(data.vattach_menu_name()),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_attachBotsUpdates.fire({});
|
|
|
|
});
|
|
|
|
}).fail([=] {
|
|
|
|
_botsRequestId = 0;
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2022-03-29 13:00:10 +04:00
|
|
|
void AttachWebView::resolve() {
|
|
|
|
if (!_bot) {
|
|
|
|
requestByUsername();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::requestByUsername() {
|
|
|
|
resolveUsername(_botUsername, [=](not_null<PeerData*> bot) {
|
|
|
|
_bot = bot->asUser();
|
|
|
|
if (!_bot || !_bot->isBot() || !_bot->botInfo->supportsAttachMenu) {
|
|
|
|
Ui::ShowMultilineToast({
|
|
|
|
// #TODO webview lang
|
|
|
|
.text = { u"This bot isn't supported in the attach menu."_q }
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
request();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::resolveUsername(
|
|
|
|
const QString &username,
|
|
|
|
Fn<void(not_null<PeerData*>)> done) {
|
|
|
|
if (const auto peer = _peer->owner().peerByUsername(username)) {
|
|
|
|
done(peer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_session->api().request(base::take(_requestId)).cancel();
|
|
|
|
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
|
|
|
|
MTP_string(username)
|
|
|
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
|
|
|
_requestId = 0;
|
|
|
|
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
|
|
|
_peer->owner().processUsers(data.vusers());
|
|
|
|
_peer->owner().processChats(data.vchats());
|
|
|
|
if (const auto peerId = peerFromMTP(data.vpeer())) {
|
|
|
|
done(_peer->owner().peer(peerId));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}).fail([=](const MTP::Error &error) {
|
|
|
|
_requestId = 0;
|
|
|
|
if (error.code() == 400) {
|
|
|
|
Ui::ShowMultilineToast({
|
|
|
|
.text = {
|
2022-03-29 13:21:10 +04:00
|
|
|
tr::lng_username_not_found(tr::now, lt_user, username),
|
|
|
|
},
|
2022-03-29 13:00:10 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::requestSimple(
|
|
|
|
not_null<UserData*> bot,
|
2022-03-29 13:21:10 +04:00
|
|
|
const WebViewButton &button) {
|
2022-03-29 13:00:10 +04:00
|
|
|
cancel();
|
|
|
|
_bot = bot;
|
|
|
|
_peer = bot;
|
|
|
|
using Flag = MTPmessages_RequestSimpleWebView::Flag;
|
|
|
|
_requestId = _session->api().request(MTPmessages_RequestSimpleWebView(
|
2022-03-29 13:21:10 +04:00
|
|
|
MTP_flags(Flag::f_theme_params),
|
2022-03-29 13:00:10 +04:00
|
|
|
bot->inputUser,
|
2022-03-29 13:21:10 +04:00
|
|
|
MTP_bytes(button.url),
|
|
|
|
MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams()))
|
2022-03-29 13:00:10 +04:00
|
|
|
)).done([=](const MTPSimpleWebViewResult &result) {
|
|
|
|
_requestId = 0;
|
|
|
|
result.match([&](const MTPDsimpleWebViewResultUrl &data) {
|
|
|
|
const auto queryId = uint64();
|
2022-03-29 13:21:10 +04:00
|
|
|
show(queryId, qs(data.vurl()), button.text);
|
2022-03-29 13:00:10 +04:00
|
|
|
});
|
|
|
|
}).fail([=](const MTP::Error &error) {
|
|
|
|
_requestId = 0;
|
|
|
|
int a = error.code();
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2022-03-29 14:34:57 +04:00
|
|
|
void AttachWebView::ClearAll() {
|
|
|
|
while (!ActiveWebViews().empty()) {
|
|
|
|
ActiveWebViews().front()->cancel();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 13:00:10 +04:00
|
|
|
void AttachWebView::show(
|
|
|
|
uint64 queryId,
|
|
|
|
const QString &url,
|
|
|
|
const QString &buttonText) {
|
|
|
|
Expects(_bot != nullptr && _peer != nullptr);
|
|
|
|
|
|
|
|
const auto close = crl::guard(this, [=] {
|
|
|
|
cancel();
|
|
|
|
});
|
|
|
|
const auto sendData = crl::guard(this, [=](QByteArray data) {
|
2022-03-29 13:21:10 +04:00
|
|
|
if (_peer != _bot) {
|
2022-03-29 13:00:10 +04:00
|
|
|
cancel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto randomId = base::RandomValue<uint64>();
|
|
|
|
_session->api().request(MTPmessages_SendWebViewData(
|
|
|
|
_bot->inputUser,
|
|
|
|
MTP_long(randomId),
|
|
|
|
MTP_string(buttonText),
|
|
|
|
MTP_bytes(data)
|
|
|
|
)).done([=](const MTPUpdates &result) {
|
|
|
|
_session->api().applyUpdates(result);
|
|
|
|
}).send();
|
|
|
|
cancel();
|
|
|
|
});
|
2022-03-29 14:34:57 +04:00
|
|
|
auto title = Info::Profile::NameValue(
|
|
|
|
_bot
|
|
|
|
) | rpl::map([](const TextWithEntities &value) {
|
|
|
|
return value.text;
|
|
|
|
});
|
|
|
|
ActiveWebViews().emplace(this);
|
2022-03-29 13:00:10 +04:00
|
|
|
_panel = Ui::BotWebView::Show({
|
|
|
|
.url = url,
|
|
|
|
.userDataPath = _session->domain().local().webviewDataPath(),
|
2022-03-29 14:34:57 +04:00
|
|
|
.title = std::move(title),
|
|
|
|
.bottom = rpl::single('@' + _bot->username),
|
2022-03-29 13:00:10 +04:00
|
|
|
.sendData = sendData,
|
|
|
|
.close = close,
|
|
|
|
.themeParams = [] { return Window::Theme::WebViewParams(); },
|
|
|
|
});
|
2022-03-29 14:34:57 +04:00
|
|
|
started(queryId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::started(uint64 queryId) {
|
|
|
|
Expects(_peer != nullptr && _bot != nullptr);
|
2022-03-29 13:00:10 +04:00
|
|
|
|
|
|
|
_session->data().webViewResultSent(
|
|
|
|
) | rpl::filter([=](const Data::Session::WebViewResultSent &sent) {
|
|
|
|
return (sent.peerId == _peer->id)
|
|
|
|
&& (sent.botId == _bot->id)
|
|
|
|
&& (sent.queryId == queryId);
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
cancel();
|
|
|
|
}, _panel->lifetime());
|
2022-03-29 14:34:57 +04:00
|
|
|
|
|
|
|
base::timer_each(
|
|
|
|
kProlongTimeout
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
using Flag = MTPmessages_ProlongWebView::Flag;
|
|
|
|
auto flags = Flag::f_reply_to_msg_id | Flag::f_silent;
|
|
|
|
_session->api().request(base::take(_prolongId)).cancel();
|
|
|
|
_prolongId = _session->api().request(MTPmessages_ProlongWebView(
|
|
|
|
MTP_flags(flags),
|
|
|
|
_peer->input,
|
|
|
|
_bot->inputUser,
|
|
|
|
MTP_long(queryId),
|
|
|
|
MTP_int(_replyToMsgId.bare)
|
|
|
|
)).done([=] {
|
|
|
|
_prolongId = 0;
|
|
|
|
}).send();
|
|
|
|
}, _panel->lifetime());
|
2022-03-29 13:00:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::requestAddToMenu(Fn<void()> callback) {
|
|
|
|
Expects(_bot != nullptr);
|
|
|
|
|
|
|
|
const auto done = [=](Fn<void()> close) {
|
|
|
|
toggleInMenu( true, [=] {
|
|
|
|
callback();
|
|
|
|
close();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const auto active = Core::App().activeWindow();
|
|
|
|
if (!active) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_confirmAddBox = active->show(Ui::MakeConfirmBox({
|
|
|
|
u"Do you want to? "_q + _bot->name,
|
|
|
|
done,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
void AttachWebView::toggleInMenu(bool enabled, Fn<void()> callback) {
|
|
|
|
Expects(_bot != nullptr);
|
|
|
|
|
|
|
|
_requestId = _session->api().request(MTPmessages_ToggleBotInAttachMenu(
|
|
|
|
_bot->inputUser,
|
|
|
|
MTP_bool(enabled)
|
2022-03-29 14:34:57 +04:00
|
|
|
)).done([=] {
|
2022-03-29 13:00:10 +04:00
|
|
|
_requestId = 0;
|
2022-03-29 16:13:54 +04:00
|
|
|
requestBots();
|
2022-03-29 13:00:10 +04:00
|
|
|
callback();
|
|
|
|
}).fail([=] {
|
|
|
|
cancel();
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2022-03-29 16:13:54 +04:00
|
|
|
std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
|
|
|
not_null<QWidget*> parent,
|
|
|
|
not_null<Window::SessionController*> controller) {
|
|
|
|
auto result = std::make_unique<Ui::DropdownMenu>(
|
|
|
|
parent,
|
|
|
|
st::dropdownMenuWithIcons);
|
|
|
|
const auto bots = &controller->session().attachWebView();
|
|
|
|
const auto raw = result.get();
|
|
|
|
const auto refresh = [=] {
|
|
|
|
raw->clearActions();
|
|
|
|
for (const auto &bot : bots->attachBots()) {
|
|
|
|
raw->addAction(bot.name, [=, bot = bot.user]{
|
|
|
|
const auto active = controller->activeChatCurrent();
|
|
|
|
if (const auto history = active.history()) {
|
|
|
|
bots->request(history->peer, bot);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
refresh();
|
|
|
|
bots->attachBotsUpdates(
|
|
|
|
) | rpl::start_with_next(refresh, raw->lifetime());
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-03-29 13:00:10 +04:00
|
|
|
} // namespace InlineBots
|