Support clipboard reading for attach menu bots.

This commit is contained in:
John Preston 2022-12-20 20:43:19 +04:00
parent 4c181b6d08
commit c647afec02
4 changed files with 100 additions and 33 deletions

View file

@ -490,7 +490,11 @@ void AttachWebView::request(const WebViewButton &button) {
)).done([=](const MTPWebViewResult &result) { )).done([=](const MTPWebViewResult &result) {
_requestId = 0; _requestId = 0;
result.match([&](const MTPDwebViewResultUrl &data) { result.match([&](const MTPDwebViewResultUrl &data) {
show(data.vquery_id().v, qs(data.vurl()), button.text); show(
data.vquery_id().v,
qs(data.vurl()),
button.text,
button.fromMenu);
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_requestId = 0; _requestId = 0;
@ -792,7 +796,8 @@ void AttachWebView::ClearAll() {
void AttachWebView::show( void AttachWebView::show(
uint64 queryId, uint64 queryId,
const QString &url, const QString &url,
const QString &buttonText) { const QString &buttonText,
bool fromMenu) {
Expects(_bot != nullptr && _action.has_value()); Expects(_bot != nullptr && _action.has_value());
const auto close = crl::guard(this, [=] { const auto close = crl::guard(this, [=] {
@ -916,6 +921,7 @@ void AttachWebView::show(
.menuButtons = buttons, .menuButtons = buttons,
.handleMenuButton = handleMenuButton, .handleMenuButton = handleMenuButton,
.themeParams = [] { return Window::Theme::WebViewParams(); }, .themeParams = [] { return Window::Theme::WebViewParams(); },
.allowClipboardRead = fromMenu,
}); });
*panel = _panel.get(); *panel = _panel.get();
started(queryId); started(queryId);
@ -1023,11 +1029,18 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
if (!PeerMatchesTypes(peer, bot.user, bot.types)) { if (!PeerMatchesTypes(peer, bot.user, bot.types)) {
continue; continue;
} }
const auto callback = [=] {
bots->request(
nullptr,
actionFactory(),
bot.user,
{ .fromMenu = true });
};
auto action = base::make_unique_q<BotAction>( auto action = base::make_unique_q<BotAction>(
raw, raw,
raw->menu()->st(), raw->menu()->st(),
bot, bot,
[=] { bots->request(nullptr, actionFactory(), bot.user, {}); }); callback);
action->forceShown( action->forceShown(
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
if (shown) { if (shown) {

View file

@ -69,6 +69,7 @@ public:
QString text; QString text;
QString startCommand; QString startCommand;
QByteArray url; QByteArray url;
bool fromMenu = false;
}; };
void request( void request(
const Api::SendAction &action, const Api::SendAction &action,
@ -127,7 +128,8 @@ private:
void show( void show(
uint64 queryId, uint64 queryId,
const QString &url, const QString &url,
const QString &buttonText = QString()); const QString &buttonText = QString(),
bool fromMenu = false);
void confirmAddToMenu( void confirmAddToMenu(
AttachWebViewBot bot, AttachWebViewBot bot,
Fn<void()> callback = nullptr); Fn<void()> callback = nullptr);

View file

@ -32,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QJsonArray> #include <QtCore/QJsonArray>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace Ui::BotWebView { namespace Ui::BotWebView {
namespace { namespace {
@ -344,7 +346,8 @@ Panel::Panel(
QString phone, QString phone,
MenuButtons menuButtons, MenuButtons menuButtons,
Fn<void(MenuButton)> handleMenuButton, Fn<void(MenuButton)> handleMenuButton,
Fn<Webview::ThemeParams()> themeParams) Fn<Webview::ThemeParams()> themeParams,
bool allowClipboardRead)
: _userDataPath(userDataPath) : _userDataPath(userDataPath)
, _handleLocalUri(std::move(handleLocalUri)) , _handleLocalUri(std::move(handleLocalUri))
, _handleInvoice(std::move(handleInvoice)) , _handleInvoice(std::move(handleInvoice))
@ -353,7 +356,8 @@ Panel::Panel(
, _phone(phone) , _phone(phone)
, _menuButtons(menuButtons) , _menuButtons(menuButtons)
, _handleMenuButton(std::move(handleMenuButton)) , _handleMenuButton(std::move(handleMenuButton))
, _widget(std::make_unique<SeparatePanel>()) { , _widget(std::make_unique<SeparatePanel>())
, _allowClipboardRead(allowClipboardRead) {
_widget->setInnerSize(st::paymentsPanelSize); _widget->setInnerSize(st::paymentsPanelSize);
_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
@ -663,6 +667,8 @@ bool Panel::createWebview() {
requestPhone(); requestPhone();
} else if (command == "web_app_setup_closing_behavior") { } else if (command == "web_app_setup_closing_behavior") {
setupClosingBehaviour(arguments); setupClosingBehaviour(arguments);
} else if (command == "web_app_read_text_from_clipboard") {
requestClipboardText(arguments);
} }
}); });
@ -737,16 +743,10 @@ void Panel::openExternalLink(const QJsonObject &args) {
LOG(("BotWebView Error: Bad 'url' in openExternalLink.")); LOG(("BotWebView Error: Bad 'url' in openExternalLink."));
_close(); _close();
return; return;
} else if (!allowOpenLink()) {
return;
} }
const auto now = crl::now(); File::OpenUrl(url);
if (_mainButtonLastClick
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
_mainButtonLastClick = 0;
File::OpenUrl(url);
} else {
const auto string = EncodeForJs(url);
_webview->window.eval(("window.open(\"" + string + "\");").toUtf8());
}
} }
void Panel::openInvoice(const QJsonObject &args) { void Panel::openInvoice(const QJsonObject &args) {
@ -816,9 +816,9 @@ void Panel::openPopup(const QJsonObject &args) {
QJsonArray{ { QJsonValue(*result.id) } } QJsonArray{ { QJsonValue(*result.id) } }
).toJson(QJsonDocument::Compact) + "[0]"; ).toJson(QJsonDocument::Compact) + "[0]";
}; };
postEvent( postEvent("popup_closed", result.id
"popup_closed", ? QJsonObject{ { u"button_id"_q, *result.id } }
result.id ? ("\"button_id\": " + safe()) : QString()); : EventData());
} }
} }
@ -841,15 +841,49 @@ void Panel::requestPhone() {
}, },
}); });
if (weak) { if (weak) {
postEvent( postEvent("phone_requested", (result.id == "share")
"phone_requested", ? QJsonObject{ { u"phone_number"_q, _phone } }
(result.id == "share" : EventData());
? "\"phone_number\": \"" + _phone + "\""
: QString()));
} }
#endif #endif
} }
void Panel::requestClipboardText(const QJsonObject &args) {
const auto requestId = args["req_id"];
if (requestId.isUndefined()) {
return;
}
auto result = QJsonObject();
result["req_id"] = requestId;
if (allowClipboardQuery()) {
result["data"] = QGuiApplication::clipboard()->text();
}
postEvent(u"clipboard_text_received"_q, result);
}
bool Panel::allowOpenLink() const {
const auto now = crl::now();
if (_mainButtonLastClick
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
_mainButtonLastClick = 0;
return true;
}
return true;
}
bool Panel::allowClipboardQuery() const {
if (!_allowClipboardRead) {
return false;
}
const auto now = crl::now();
if (_mainButtonLastClick
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
_mainButtonLastClick = 0;
return true;
}
return true;
}
void Panel::scheduleCloseWithConfirmation() { void Panel::scheduleCloseWithConfirmation() {
if (!_closeWithConfirmationScheduled) { if (!_closeWithConfirmationScheduled) {
_closeWithConfirmationScheduled = true; _closeWithConfirmationScheduled = true;
@ -1031,16 +1065,17 @@ void Panel::updateThemeParams(const Webview::ThemeParams &params) {
params.scrollBgOver, params.scrollBgOver,
params.scrollBarBg, params.scrollBarBg,
params.scrollBarBgOver); params.scrollBarBgOver);
postEvent("theme_changed", "\"theme_params\": " + params.json); postEvent("theme_changed", "{\"theme_params\": " + params.json + "}");
} }
void Panel::invoiceClosed(const QString &slug, const QString &status) { void Panel::invoiceClosed(const QString &slug, const QString &status) {
if (!_webview || !_webview->window.widget()) { if (!_webview || !_webview->window.widget()) {
return; return;
} }
postEvent( postEvent("invoice_closed", QJsonObject{
"invoice_closed", { u"slug"_q, slug },
"\"slug\": \"" + slug + "\", \"status\": \"" + status + "\""); { u"status"_q, status },
});
_widget->showAndActivate(); _widget->showAndActivate();
_hiddenForPayment = false; _hiddenForPayment = false;
} }
@ -1050,13 +1085,21 @@ void Panel::hideForPayment() {
_widget->hideGetDuration(); _widget->hideGetDuration();
} }
void Panel::postEvent(const QString &event, const QString &data) { void Panel::postEvent(const QString &event) {
postEvent(event, {});
}
void Panel::postEvent(const QString &event, EventData data) {
auto written = v::is<QString>(data)
? v::get<QString>(data).toUtf8()
: QJsonDocument(
v::get<QJsonObject>(data)).toJson(QJsonDocument::Compact);
_webview->window.eval(R"( _webview->window.eval(R"(
if (window.TelegramGameProxy) { if (window.TelegramGameProxy) {
window.TelegramGameProxy.receiveEvent( window.TelegramGameProxy.receiveEvent(
")" ")"
+ event.toUtf8() + event.toUtf8()
+ '"' + (data.isEmpty() ? QByteArray() : ", {" + data.toUtf8() + '}') + '"' + (written.isEmpty() ? QByteArray() : ", " + written)
+ R"(); + R"();
} }
)"); )");
@ -1110,7 +1153,8 @@ std::unique_ptr<Panel> Show(Args &&args) {
args.phone, args.phone,
args.menuButtons, args.menuButtons,
std::move(args.handleMenuButton), std::move(args.handleMenuButton),
std::move(args.themeParams)); std::move(args.themeParams),
args.allowClipboardRead);
if (!result->showWebview(args.url, params, std::move(args.bottom))) { if (!result->showWebview(args.url, params, std::move(args.bottom))) {
const auto available = Webview::Availability(); const auto available = Webview::Availability();
if (available.error != Webview::Available::Error::None) { if (available.error != Webview::Available::Error::None) {

View file

@ -52,7 +52,8 @@ public:
QString phone, QString phone,
MenuButtons menuButtons, MenuButtons menuButtons,
Fn<void(MenuButton)> handleMenuButton, Fn<void(MenuButton)> handleMenuButton,
Fn<Webview::ThemeParams()> themeParams); Fn<Webview::ThemeParams()> themeParams,
bool allowClipboardRead);
~Panel(); ~Panel();
void requestActivate(); void requestActivate();
@ -94,13 +95,18 @@ private:
void openInvoice(const QJsonObject &args); void openInvoice(const QJsonObject &args);
void openPopup(const QJsonObject &args); void openPopup(const QJsonObject &args);
void requestPhone(); void requestPhone();
void requestClipboardText(const QJsonObject &args);
void setupClosingBehaviour(const QJsonObject &args); void setupClosingBehaviour(const QJsonObject &args);
void createMainButton(); void createMainButton();
void scheduleCloseWithConfirmation(); void scheduleCloseWithConfirmation();
void closeWithConfirmation(); void closeWithConfirmation();
void postEvent(const QString &event, const QString &data = {}); using EventData = std::variant<QString, QJsonObject>;
void postEvent(const QString &event);
void postEvent(const QString &event, EventData data);
[[nodiscard]] bool allowOpenLink() const;
[[nodiscard]] bool allowClipboardQuery() const;
[[nodiscard]] bool progressWithBackground() const; [[nodiscard]] bool progressWithBackground() const;
[[nodiscard]] QRect progressRect() const; [[nodiscard]] QRect progressRect() const;
void setupProgressGeometry(); void setupProgressGeometry();
@ -119,7 +125,7 @@ private:
std::unique_ptr<RpWidget> _webviewBottom; std::unique_ptr<RpWidget> _webviewBottom;
QPointer<QWidget> _webviewParent; QPointer<QWidget> _webviewParent;
std::unique_ptr<Button> _mainButton; std::unique_ptr<Button> _mainButton;
crl::time _mainButtonLastClick = 0; mutable crl::time _mainButtonLastClick = 0;
std::unique_ptr<Progress> _progress; std::unique_ptr<Progress> _progress;
rpl::event_stream<> _themeUpdateForced; rpl::event_stream<> _themeUpdateForced;
rpl::lifetime _fgLifetime; rpl::lifetime _fgLifetime;
@ -128,6 +134,7 @@ private:
bool _themeUpdateScheduled = false; bool _themeUpdateScheduled = false;
bool _hiddenForPayment = false; bool _hiddenForPayment = false;
bool _closeWithConfirmationScheduled = false; bool _closeWithConfirmationScheduled = false;
bool _allowClipboardRead = false;
}; };
@ -144,6 +151,7 @@ struct Args {
MenuButtons menuButtons; MenuButtons menuButtons;
Fn<void(MenuButton)> handleMenuButton; Fn<void(MenuButton)> handleMenuButton;
Fn<Webview::ThemeParams()> themeParams; Fn<Webview::ThemeParams()> themeParams;
bool allowClipboardRead = false;
}; };
[[nodiscard]] std::unique_ptr<Panel> Show(Args &&args); [[nodiscard]] std::unique_ptr<Panel> Show(Args &&args);