Implement separate instances for web apps.

This commit is contained in:
John Preston 2024-07-19 14:06:30 +02:00
parent 9461095c88
commit fd982b90db
14 changed files with 1202 additions and 1084 deletions

View File

@ -488,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::WebView: {
if (const auto bot = item->getMessageBot()) {
bot->session().attachWebView().request(
controller,
Api::SendAction(bot->owner().history(bot)),
bot,
{ .text = button->text, .url = button->data });
bot->session().attachWebView().open({
.bot = bot,
.context = { .controller = controller },
.button = { .text = button->text, .url = button->data },
.source = InlineBots::WebViewSourceButton{ .simple = false },
});
}
} break;
case ButtonType::SimpleWebView: {
if (const auto bot = item->getMessageBot()) {
bot->session().attachWebView().requestSimple(
controller,
bot,
{ .text = button->text, .url = button->data });
bot->session().attachWebView().open({
.bot = bot,
.context = { .controller = controller },
.button = {.text = button->text, .url = button->data },
.source = InlineBots::WebViewSourceButton{ .simple = true },
});
}
} break;
}

View File

@ -231,7 +231,11 @@ Application::~Application() {
// For example Domain::removeRedundantAccounts() is called from
// Domain::finish() and there is a violation on Ensures(started()).
Payments::CheckoutProcess::ClearAll();
InlineBots::AttachWebView::ClearAll();
for (const auto &[index, account] : _domain->accounts()) {
if (account->sessionExists()) {
account->session().attachWebView().closeAll();
}
}
_iv->closeAll();
_domain->finish();

View File

@ -191,11 +191,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
const auto title = game->title;
const auto itemId = my.itemId;
const auto openGame = [=] {
bot->session().attachWebView().showGame({
bot->session().attachWebView().open({
.bot = bot,
.context = itemId,
.url = url,
.title = title,
.button = {.url = url.toUtf8() },
.source = InlineBots::WebViewSourceGame{
.messageId = itemId,
.title = title,
},
});
};
if (_bot->isVerified()

View File

@ -21,6 +21,10 @@ namespace Ui {
class Show;
} // namespace Ui
namespace InlineBots {
struct WebViewContext;
} // namespace InlineBots
namespace Main {
class Session;
} // namespace Main
@ -38,10 +42,10 @@ class SessionController;
class PeerData;
struct ClickHandlerContext {
FullMsgId itemId;
QString attachBotWebviewUrl;
// Is filled from sections.
Fn<HistoryView::ElementDelegate*()> elementDelegate;
base::weak_ptr<Window::SessionController> sessionWindow;
std::shared_ptr<InlineBots::WebViewContext> botWebviewContext;
std::shared_ptr<Ui::Show> show;
bool mayShowConfirmation = false;
bool skipBotAutoLogin = false;

View File

@ -573,8 +573,11 @@ bool ResolveUsernameOrPhone(
: (appname.isEmpty() && params.contains(u"startapp"_q))
? params.value(u"startapp"_q)
: std::optional<QString>()),
.attachBotMenuOpen = (appname.isEmpty()
.attachBotMainOpen = (appname.isEmpty()
&& params.contains(u"startapp"_q)),
.attachBotMainCompact = (appname.isEmpty()
&& params.contains(u"startapp"_q)
&& (params.value(u"mode"_q) == u"compact"_q)),
.attachBotChooseTypes = InlineBots::ParseChooseTypes(
params.value(u"choose"_q)),
.voicechatHash = (params.contains(u"livestream"_q)
@ -585,7 +588,7 @@ bool ResolveUsernameOrPhone(
? std::make_optional(params.value(u"voicechat"_q))
: std::nullopt),
.clickFromMessageId = myContext.itemId,
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
.clickFromBotWebviewContext = myContext.botWebviewContext,
});
return true;
}
@ -626,7 +629,7 @@ bool ResolvePrivatePost(
}
: Window::RepliesByLinkInfo{ v::null },
.clickFromMessageId = my.itemId,
.clickFromAttachBotWebviewUrl = my.attachBotWebviewUrl,
.clickFromBotWebviewContext = my.botWebviewContext,
});
controller->window().activate();
return true;
@ -1178,7 +1181,7 @@ bool ResolveChatLink(
controller->showPeerByLink(Window::PeerByLinkInfo{
.chatLinkSlug = match->captured(1),
.clickFromMessageId = myContext.itemId,
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
.clickFromBotWebviewContext = myContext.botWebviewContext,
});
return true;
}

View File

@ -4940,7 +4940,14 @@ bool HistoryWidget::updateCmdStartShown() {
const auto user = _peer ? _peer->asUser() : nullptr;
const auto bot = (user && user->isBot()) ? user : nullptr;
if (bot && !bot->botInfo->botMenuButtonUrl.isEmpty()) {
session().attachWebView().requestMenu(controller(), bot);
session().attachWebView().open({
.bot = bot,
.context = { .controller = controller() },
.button = {
.url = bot->botInfo->botMenuButtonUrl.toUtf8(),
},
.source = InlineBots::WebViewSourceBotMenu(),
});
} else if (!_fieldAutocomplete->isHidden()) {
_fieldAutocomplete->hideAnimated();
} else {

File diff suppressed because it is too large Load Diff

View File

@ -10,15 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flags.h"
#include "base/timer.h"
#include "base/weak_ptr.h"
#include "dialogs/dialogs_key.h"
#include "api/api_common.h"
#include "mtproto/sender.h"
#include "ui/chat/attach/attach_bot_webview.h"
#include "ui/rp_widget.h"
namespace Api {
struct SendAction;
} // namespace Api
namespace Data {
class Thread;
} // namespace Data
namespace Ui {
class Show;
class GenericBox;
class DropdownMenu;
} // namespace Ui
@ -47,6 +50,8 @@ enum class CheckoutResult;
namespace InlineBots {
class WebViewInstance;
enum class PeerType : uint8 {
SameBot = 0x01,
Bot = 0x02,
@ -86,95 +91,176 @@ struct AddToMenuOpenApp {
not_null<BotAppData*> app;
QString startCommand;
};
using AddToMenuOpen = std::variant<
struct AddToMenuOpen : std::variant<
AddToMenuOpenAttach,
AddToMenuOpenMenu,
AddToMenuOpenApp>;
AddToMenuOpenApp> {
using variant::variant;
};
class AttachWebView final
struct WebViewSourceButton {
bool simple = false;
friend inline bool operator==(
WebViewSourceButton,
WebViewSourceButton) = default;
};
struct WebViewSourceSwitch {
friend inline bool operator==(
const WebViewSourceSwitch &,
const WebViewSourceSwitch &) = default;
};
struct WebViewSourceLinkApp { // t.me/botusername/appname
base::weak_ptr<WebViewInstance> from;
QString appname;
QString token;
friend inline bool operator==(
const WebViewSourceLinkApp &,
const WebViewSourceLinkApp &) = default;
};
struct WebViewSourceLinkAttachMenu { // ?startattach
base::weak_ptr<WebViewInstance> from;
base::weak_ptr<Data::Thread> thread;
PeerTypes choose;
QString token;
friend inline bool operator==(
const WebViewSourceLinkAttachMenu &,
const WebViewSourceLinkAttachMenu &) = default;
};
struct WebViewSourceLinkBotProfile { // t.me/botusername?startapp
base::weak_ptr<WebViewInstance> from;
QString token;
bool compact = false;
friend inline bool operator==(
const WebViewSourceLinkBotProfile &,
const WebViewSourceLinkBotProfile &) = default;
};
struct WebViewSourceMainMenu {
friend inline bool operator==(
WebViewSourceMainMenu,
WebViewSourceMainMenu) = default;
};
struct WebViewSourceAttachMenu {
base::weak_ptr<Data::Thread> thread;
friend inline bool operator==(
const WebViewSourceAttachMenu &,
const WebViewSourceAttachMenu &) = default;
};
struct WebViewSourceBotMenu {
friend inline bool operator==(
WebViewSourceBotMenu,
WebViewSourceBotMenu) = default;
};
struct WebViewSourceGame {
FullMsgId messageId;
QString title;
friend inline bool operator==(
WebViewSourceGame,
WebViewSourceGame) = default;
};
struct WebViewSourceBotProfile {
friend inline bool operator==(
WebViewSourceBotProfile,
WebViewSourceBotProfile) = default;
};
struct WebViewSource : std::variant<
WebViewSourceButton,
WebViewSourceSwitch,
WebViewSourceLinkApp,
WebViewSourceLinkAttachMenu,
WebViewSourceLinkBotProfile,
WebViewSourceMainMenu,
WebViewSourceAttachMenu,
WebViewSourceBotMenu,
WebViewSourceGame,
WebViewSourceBotProfile> {
using variant::variant;
};
struct WebViewButton {
QString text;
QString startCommand;
QByteArray url;
bool fromAttachMenu = false;
bool fromMainMenu = false;
bool fromSwitch = false;
};
struct WebViewContext {
base::weak_ptr<Window::SessionController> controller;
Dialogs::EntryState dialogsEntryState;
std::optional<Api::SendAction> action;
bool maySkipConfirmation = false;
};
struct WebViewDescriptor {
not_null<UserData*> bot;
std::shared_ptr<Ui::Show> parentShow;
WebViewContext context;
WebViewButton button;
WebViewSource source;
};
class WebViewInstance final
: public base::has_weak_ptr
, public Ui::BotWebView::Delegate {
public:
explicit AttachWebView(not_null<Main::Session*> session);
~AttachWebView();
explicit WebViewInstance(WebViewDescriptor &&descriptor);
~WebViewInstance();
struct WebViewButton {
QString text;
QString startCommand;
QByteArray url;
bool fromAttachMenu = false;
bool fromMainMenu = false;
bool fromSwitch = false;
};
void request(
not_null<Window::SessionController*> controller,
const Api::SendAction &action,
const QString &botUsername,
const QString &startCommand);
void request(
not_null<Window::SessionController*> controller,
const Api::SendAction &action,
not_null<UserData*> bot,
const WebViewButton &button);
void requestSimple(
not_null<Window::SessionController*> controller,
not_null<UserData*> bot,
const WebViewButton &button);
void requestMenu(
not_null<Window::SessionController*> controller,
not_null<UserData*> bot);
void requestApp(
not_null<Window::SessionController*> controller,
const Api::SendAction &action,
not_null<UserData*> bot,
const QString &appName,
const QString &startParam,
bool forceConfirmation);
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<UserData*> bot() const;
[[nodiscard]] WebViewSource source() const;
void cancel();
void requestBots(Fn<void()> callback = nullptr);
[[nodiscard]] const std::vector<AttachWebViewBot> &attachBots() const {
return _attachBots;
}
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
return _attachBotsUpdates.events();
}
void notifyBotIconLoaded() {
_attachBotsUpdates.fire({});
}
[[nodiscard]] bool disclaimerAccepted(
const AttachWebViewBot &bot) const;
[[nodiscard]] bool showMainMenuNewBadge(
const AttachWebViewBot &bot) const;
void requestAddToMenu(
not_null<UserData*> bot,
AddToMenuOpen open);
void requestAddToMenu(
not_null<UserData*> bot,
AddToMenuOpen open,
Window::SessionController *controller,
std::optional<Api::SendAction> action);
void removeFromMenu(not_null<UserData*> bot);
[[nodiscard]] std::optional<Api::SendAction> lookupLastAction(
const QString &url) const;
struct ShowGameParams {
not_null<UserData*> bot;
FullMsgId context;
QString url;
QString title;
};
void showGame(ShowGameParams &&params);
void activate();
void close();
[[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow();
static void ClearAll();
private:
struct Context;
void resolve();
bool openAppFromBotMenuLink();
void requestButton();
void requestSimple();
void requestApp(bool allowWrite);
void requestWithMainMenuDisclaimer();
void requestWithMenuAdd();
void maybeChooseAndRequestButton(PeerTypes supported);
void resolveApp(
const QString &appname,
const QString &startparam,
bool forceConfirmation);
void confirmOpen(Fn<void()> done);
void confirmAppOpen(bool writeAccess, Fn<void(bool allowWrite)> done);
void show(const QString &url, uint64 queryId = 0);
void showGame();
void started(uint64 queryId);
[[nodiscard]] Window::SessionController *windowForThread(
not_null<Data::Thread*> thread);
auto nonPanelPaymentFormFactory(
Fn<void(Payments::CheckoutResult)> reactivate)
-> Fn<void(Payments::NonPanelPaymentForm)>;
Webview::ThemeParams botThemeParams() override;
bool botHandleLocalUri(QString uri, bool keepOpen) override;
@ -194,35 +280,81 @@ private:
void botShareGameScore() override;
void botClose() override;
[[nodiscard]] static Context LookupContext(
not_null<Window::SessionController*> controller,
const Api::SendAction &action);
[[nodiscard]] static bool IsSame(
const std::unique_ptr<Context> &a,
const Context &b);
const std::shared_ptr<Ui::Show> _parentShow;
const not_null<Main::Session*> _session;
const not_null<UserData*> _bot;
const WebViewContext _context;
const WebViewButton _button;
const WebViewSource _source;
bool openAppFromMenuLink(
BotAppData *_app = nullptr;
QString _appStartParam;
bool _dataSent = false;
mtpRequestId _requestId = 0;
mtpRequestId _prolongId = 0;
QString _panelUrl;
std::unique_ptr<Ui::BotWebView::Panel> _panel;
static base::weak_ptr<WebViewInstance> PendingActivation;
};
class AttachWebView final : public base::has_weak_ptr {
public:
explicit AttachWebView(not_null<Main::Session*> session);
~AttachWebView();
void open(WebViewDescriptor &&descriptor);
void openByUsername(
not_null<Window::SessionController*> controller,
const Api::SendAction &action,
const QString &botUsername,
const QString &startCommand);
void cancel();
void requestBots(Fn<void()> callback = nullptr);
[[nodiscard]] const std::vector<AttachWebViewBot> &attachBots() const {
return _attachBots;
}
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
return _attachBotsUpdates.events();
}
void notifyBotIconLoaded() {
_attachBotsUpdates.fire({});
}
[[nodiscard]] bool disclaimerAccepted(
const AttachWebViewBot &bot) const;
[[nodiscard]] bool showMainMenuNewBadge(
const AttachWebViewBot &bot) const;
void removeFromMenu(
std::shared_ptr<Ui::Show> show,
not_null<UserData*> bot);
void requestWithOptionalConfirm(
enum class AddToMenuResult {
AlreadyInMenu,
Added,
Unsupported,
Cancelled,
};
void requestAddToMenu(
not_null<UserData*> bot,
const WebViewButton &button,
const Context &context,
Window::SessionController *controllerForConfirm = nullptr);
void resolve();
void request(const WebViewButton &button);
void requestSimple(const WebViewButton &button);
void resolveUsername(
const QString &username,
Fn<void(not_null<PeerData*>)> done);
void confirmOpen(
not_null<Window::SessionController*> controller,
Fn<void()> done);
Fn<void(AddToMenuResult, PeerTypes supported)> done);
void acceptMainMenuDisclaimer(
not_null<Window::SessionController*> controller,
const WebViewButton &button);
std::shared_ptr<Ui::Show> show,
not_null<UserData*> bot,
Fn<void(AddToMenuResult, PeerTypes supported)> done);
void close(not_null<WebViewInstance*> instance);
void closeAll();
private:
void resolveUsername(
std::shared_ptr<Ui::Show> show,
Fn<void(not_null<PeerData*>)> done);
enum class ToggledState {
Removed,
@ -232,67 +364,35 @@ private:
void toggleInMenu(
not_null<UserData*> bot,
ToggledState state,
Fn<void()> callback = nullptr);
void show(
uint64 queryId,
const QString &url,
const QString &buttonText = QString(),
bool allowClipboardRead = false,
const BotAppData *app = nullptr,
bool fromMainMenu = false);
Fn<void(bool success)> callback = nullptr);
void confirmAddToMenu(
AttachWebViewBot bot,
Fn<void()> callback = nullptr);
void confirmAppOpen(bool requestWriteAccess);
void requestAppView(bool allowWrite);
void started(uint64 queryId);
void showToast(
const QString &text,
Window::SessionController *controller = nullptr);
Fn<void(Payments::NonPanelPaymentForm)> nonPanelPaymentFormFactory(
Fn<void(Payments::CheckoutResult)> reactivate);
Fn<void(bool added)> callback = nullptr);
const not_null<Main::Session*> _session;
base::Timer _refreshTimer;
std::unique_ptr<Context> _context;
std::unique_ptr<Context> _lastShownContext;
QString _lastShownUrl;
uint64 _lastShownQueryId = 0;
QString _lastShownButtonText;
UserData *_bot = nullptr;
QString _botUsername;
QString _botAppName;
QString _startCommand;
BotAppData *_app = nullptr;
QPointer<Ui::GenericBox> _confirmAddBox;
bool _appConfirmationRequired = false;
bool _appRequestWriteAccess = false;
mtpRequestId _requestId = 0;
mtpRequestId _prolongId = 0;
uint64 _botsHash = 0;
mtpRequestId _botsRequestId = 0;
std::vector<Fn<void()>> _botsRequestCallbacks;
std::unique_ptr<Context> _addToMenuContext;
UserData *_addToMenuBot = nullptr;
mtpRequestId _addToMenuId = 0;
AddToMenuOpen _addToMenuOpen;
base::weak_ptr<Window::SessionController> _addToMenuChooseController;
struct AddToMenuProcess {
mtpRequestId requestId = 0;
std::vector<Fn<void(AddToMenuResult, PeerTypes supported)>> done;
};
base::flat_map<not_null<UserData*>, AddToMenuProcess> _addToMenu;
std::vector<AttachWebViewBot> _attachBots;
rpl::event_stream<> _attachBotsUpdates;
base::flat_set<not_null<UserData*>> _disclaimerAccepted;
FullMsgId _gameContext;
std::unique_ptr<Ui::BotWebView::Panel> _panel;
bool _catchingCancelInShowCall = false;
std::vector<std::unique_ptr<WebViewInstance>> _instances;
};

View File

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/path_shift_gradient.h"
#include "ui/painter.h"
#include "history/view/history_view_cursor_state.h"
#include "history/history.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h"
@ -677,10 +678,13 @@ void Inner::switchPm() {
if (!_inlineBot || !_inlineBot->isBot()) {
return;
} else if (!_switchPmUrl.isEmpty()) {
_inlineBot->session().attachWebView().requestSimple(
_controller,
_inlineBot,
{ .url = _switchPmUrl, .fromSwitch = true });
const auto bot = _inlineBot;
_inlineBot->session().attachWebView().open({
.bot = bot,
.context = { .controller = _controller },
.button = { .url = _switchPmUrl },
.source = InlineBots::WebViewSourceSwitch(),
});
} else {
_inlineBot->botInfo->startToken = _switchPmStartToken;
_inlineBot->botInfo->inlineReturnTo

View File

@ -193,7 +193,7 @@ void MainWindow::setupPasscodeLock() {
setInnerFocus();
}
if (const auto sessionController = controller().sessionController()) {
sessionController->session().attachWebView().cancel();
sessionController->session().attachWebView().closeAll();
}
}

View File

@ -1282,6 +1282,27 @@ void Panel::showBox(
}
}
}
const auto raw = box.data();
InvokeQueued(raw, [=] {
if (raw->window()->isActiveWindow()) {
// In case focus is somewhat in a native child window,
// like a webview, Qt glitches here with input fields showing
// focused state, but not receiving any keyboard input:
//
// window()->windowHandle()->isActive() == false.
//
// Steps were: SeparatePanel with a WebView2 child,
// some interaction with mouse inside the WebView2,
// so that WebView2 gets focus and active window state,
// then we call setSearchAllowed() and after animation
// is finished try typing -> nothing happens.
//
// With this workaround it works fine.
_widget->activateWindow();
}
});
_widget->showBox(
std::move(box),
LayerOption::KeepOther,

View File

@ -367,12 +367,15 @@ void SetupMenuBots(
(height - icon->height()) / 2);
}, button->lifetime());
const auto weak = Ui::MakeWeak(container);
const auto show = controller->uiShow();
button->setAcceptBoth(true);
button->clicks(
) | rpl::start_with_next([=](Qt::MouseButton which) {
if (which == Qt::LeftButton) {
bots->requestSimple(controller, user, {
.fromMainMenu = true,
bots->open({
.bot = user,
.context = { .controller = controller },
.source = InlineBots::WebViewSourceMainMenu(),
});
if (weak) {
controller->window().hideSettingsAndLayer();
@ -384,7 +387,7 @@ void SetupMenuBots(
st::popupMenuWithIcons);
(*menu)->addAction(
tr::lng_bot_remove_from_menu(tr::now),
[=] { bots->removeFromMenu(user); },
[=] { bots->removeFromMenu(show, user); },
&st::menuIconDelete);
(*menu)->popup(QCursor::pos());
}

View File

@ -614,17 +614,23 @@ void SessionNavigation::showPeerByLinkResolved(
const auto contextPeer = item
? item->history()->peer
: bot;
const auto action = bot->session().attachWebView().lookupLastAction(
info.clickFromAttachBotWebviewUrl
).value_or(Api::SendAction(bot->owner().history(contextPeer)));
const auto action = info.clickFromBotWebviewContext
? info.clickFromBotWebviewContext->action
: Api::SendAction(bot->owner().history(contextPeer));
crl::on_main(this, [=] {
bot->session().attachWebView().requestApp(
parentController(),
action,
bot,
info.botAppName,
info.startToken,
info.botAppForceConfirmation);
bot->session().attachWebView().open({
.bot = bot,
.context = {
.controller = parentController(),
.action = action,
.maySkipConfirmation = !info.botAppForceConfirmation,
},
.button = { .startCommand = info.startToken },
.source = InlineBots::WebViewSourceLinkApp{
.appname = info.botAppName,
.token = info.startToken,
},
});
});
} else if (bot && resolveType == ResolveType::ShareGame) {
Window::ShowShareGameBox(parentController(), bot, info.startToken);
@ -672,20 +678,25 @@ void SessionNavigation::showPeerByLinkResolved(
crl::on_main(this, [=] {
const auto history = peer->owner().history(peer);
showPeerHistory(history, params, msgId);
peer->session().attachWebView().request(
peer->session().attachWebView().openByUsername(
parentController(),
Api::SendAction(history),
attachBotUsername,
info.attachBotToggleCommand.value_or(QString()));
});
} else if (bot && info.attachBotMenuOpen) {
} else if (bot && info.attachBotMainOpen) {
const auto startCommand = info.attachBotToggleCommand.value_or(
QString());
bot->session().attachWebView().requestAddToMenu(
bot,
InlineBots::AddToMenuOpenMenu{ startCommand },
parentController(),
std::optional<Api::SendAction>());
bot->session().attachWebView().open({
.bot = bot,
.context = { .controller = parentController() },
.button = { .startCommand = startCommand },
.source = InlineBots::WebViewSourceLinkBotProfile{
.token = startCommand,
.compact = info.attachBotMainCompact,
},
});
} else if (bot && info.attachBotToggleCommand) {
const auto itemId = info.clickFromMessageId;
const auto item = _session->data().message(itemId);
@ -695,17 +706,21 @@ void SessionNavigation::showPeerByLinkResolved(
const auto contextUser = contextPeer
? contextPeer->asUser()
: nullptr;
bot->session().attachWebView().requestAddToMenu(
bot,
InlineBots::AddToMenuOpenAttach{
.startCommand = *info.attachBotToggleCommand,
.chooseTypes = info.attachBotChooseTypes,
bot->session().attachWebView().open({
.bot = bot,
.context = {
.controller = parentController(),
.action = (contextUser
? Api::SendAction(
contextUser->owner().history(contextUser))
: std::optional<Api::SendAction>()),
},
parentController(),
(contextUser
? Api::SendAction(
contextUser->owner().history(contextUser))
: std::optional<Api::SendAction>()));
.button = { .startCommand = *info.attachBotToggleCommand },
.source = InlineBots::WebViewSourceLinkAttachMenu{
.choose = info.attachBotChooseTypes,
.token = *info.attachBotToggleCommand,
},
});
} else {
const auto draft = info.text;
crl::on_main(this, [=] {

View File

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace InlineBots {
struct WebViewContext;
} // namespace InlineBots
namespace Window {
enum class ResolveType {
@ -45,11 +49,12 @@ struct PeerByLinkInfo {
bool botAppForceConfirmation = false;
QString attachBotUsername;
std::optional<QString> attachBotToggleCommand;
bool attachBotMenuOpen = false;
bool attachBotMainOpen = false;
bool attachBotMainCompact = false;
InlineBots::PeerTypes attachBotChooseTypes;
std::optional<QString> voicechatHash;
FullMsgId clickFromMessageId;
QString clickFromAttachBotWebviewUrl;
std::shared_ptr<InlineBots::WebViewContext> clickFromBotWebviewContext;
};
} // namespace Window