Migrate games to AttachWebView.

This commit is contained in:
John Preston 2024-07-09 15:47:14 +02:00
parent 78093173a9
commit 6effac7915
11 changed files with 223 additions and 223 deletions

View file

@ -127,11 +127,7 @@ void SendBotCallbackData(
UrlClickHandler::Open(link);
return;
}
const auto scoreLink = AppendShareGameScoreUrl(
session,
link,
item->fullId());
BotGameUrlClickHandler(bot, scoreLink).onClick({
BotGameUrlClickHandler(bot, link).onClick({
Qt::LeftButton,
QVariant::fromValue(ClickHandlerContext{
.itemId = item->fullId(),

View file

@ -1409,55 +1409,6 @@ std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const {
return result;
}
QString AppendShareGameScoreUrl(
not_null<Main::Session*> session,
const QString &url,
const FullMsgId &fullId) {
auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data());
const auto peer = fullId.peer
? session->data().peerLoaded(fullId.peer)
: static_cast<PeerData*>(nullptr);
const auto channelAccessHash = uint64((peer && peer->isChannel())
? peer->asChannel()->access
: 0);
shareHashDataInts[0] = session->userId().bare;
shareHashDataInts[1] = fullId.peer.value;
shareHashDataInts[2] = uint64(fullId.msg.bare);
shareHashDataInts[3] = channelAccessHash;
// Count SHA1() of data.
auto key128Size = 0x10;
auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
//// Mix in channel access hash to the first 64 bits of SHA1 of data.
//*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash;
// Encrypt data.
if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
return url;
}
auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash);
auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl);
auto hashPosition = url.indexOf('#');
if (hashPosition < 0) {
return url + '#' + shareComponent;
}
auto hash = url.mid(hashPosition + 1);
if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) {
return url + '&' + shareComponent;
}
if (!hash.isEmpty()) {
return url + '?' + shareComponent;
}
return url + shareComponent;
}
ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
const std::vector<not_null<Data::Thread*>> &result,
const MessageIdsList &msgIds) {
@ -1612,9 +1563,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
}
void FastShareMessage(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto show = controller->uiShow();
const auto history = item->history();
const auto owner = &history->owner();
const auto session = &history->session();
@ -1643,7 +1593,7 @@ void FastShareMessage(
}
if (item->hasDirectLink()) {
using namespace HistoryView;
CopyPostLink(controller, item->fullId(), Context::History);
CopyPostLink(show, item->fullId(), Context::History);
} else if (const auto bot = item->getMessageBot()) {
if (const auto media = item->media()) {
if (const auto game = media->game()) {
@ -1675,23 +1625,27 @@ void FastShareMessage(
auto copyLinkCallback = canCopyLink
? Fn<void()>(std::move(copyCallback))
: Fn<void()>();
controller->show(
Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyLinkCallback),
.submitCallback = ShareBox::DefaultForwardCallback(
show,
history,
msgIds),
.filterCallback = std::move(filterCallback),
.forwardOptions = {
.sendersCount = ItemsForwardSendersCount(items),
.captionsCount = ItemsForwardCaptionsCount(items),
.show = !hasOnlyForcedForwardedInfo,
},
.premiumRequiredError = SharePremiumRequiredError(),
}),
Ui::LayerOption::CloseOther);
show->show(Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyLinkCallback),
.submitCallback = ShareBox::DefaultForwardCallback(
show,
history,
msgIds),
.filterCallback = std::move(filterCallback),
.forwardOptions = {
.sendersCount = ItemsForwardSendersCount(items),
.captionsCount = ItemsForwardCaptionsCount(items),
.show = !hasOnlyForcedForwardedInfo,
},
.premiumRequiredError = SharePremiumRequiredError(),
}), Ui::LayerOption::CloseOther);
}
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
FastShareMessage(controller->uiShow(), item);
}
void FastShareLink(
@ -1793,111 +1747,3 @@ auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError;
}
void ShareGameScoreByHash(
not_null<Window::SessionController*> controller,
const QString &hash) {
auto &session = controller->session();
auto key128Size = 0x10;
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
controller->show(
Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()),
Ui::LayerOption::CloseOther);
return;
}
// Decrypt data.
auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized);
if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) {
return;
}
// Count SHA1() of data.
char dataSha1[20] = { 0 };
hashSha1(hashData.constData(), hashData.size(), dataSha1);
//// Mix out channel access hash from the first 64 bits of SHA1 of data.
//auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
//// Check next 64 bits of SHA1() of data.
//auto skipSha1Part = sizeof(channelAccessHash);
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
// return;
//}
// Check 128 bits of SHA1() of data.
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
if (hashDataInts[0] != session.userId().bare) {
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
const auto peerId = PeerId(hashDataInts[1]);
const auto channelAccessHash = hashDataInts[3];
if (!peerIsChannel(peerId) && channelAccessHash) {
// If there is no channel id, there should be no channel access_hash.
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
const auto msgId = MsgId(int64(hashDataInts[2]));
if (const auto item = session.data().message(peerId, msgId)) {
FastShareMessage(controller, item);
} else {
const auto weak = base::make_weak(controller);
const auto resolveMessageAndShareScore = crl::guard(weak, [=](
PeerData *peer) {
auto done = crl::guard(weak, [=] {
const auto item = weak->session().data().message(
peerId,
msgId);
if (item) {
FastShareMessage(weak.get(), item);
} else {
weak->show(
Ui::MakeInformBox(tr::lng_edit_deleted()),
Ui::LayerOption::CloseOther);
}
});
auto &api = weak->session().api();
api.requestMessageData(peer, msgId, std::move(done));
});
const auto peer = peerIsChannel(peerId)
? controller->session().data().peerLoaded(peerId)
: nullptr;
if (peer || !peerIsChannel(peerId)) {
resolveMessageAndShareScore(peer);
} else {
const auto owner = &controller->session().data();
controller->session().api().request(MTPchannels_GetChannels(
MTP_vector<MTPInputChannel>(
1,
MTP_inputChannel(
MTP_long(peerToChannel(peerId).bare),
MTP_long(channelAccessHash)))
)).done([=](const MTPmessages_Chats &result) {
result.match([&](const auto &data) {
owner->processChats(data.vchats());
});
if (const auto peer = owner->peerLoaded(peerId)) {
resolveMessageAndShareScore(peer);
}
}).send();
}
}
}

View file

@ -59,13 +59,11 @@ class SlideWrap;
class PopupMenu;
} // namespace Ui
QString AppendShareGameScoreUrl(
not_null<Main::Session*> session,
const QString &url,
const FullMsgId &fullId);
void ShareGameScoreByHash(
not_null<Window::SessionController*> controller,
const QString &hash);
class ShareBox;
void FastShareMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item);
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);

View file

@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/history_item.h"
#include "inline_bots/bot_attach_web_view.h"
#include "data/data_game.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "window/window_controller.h"
@ -171,23 +173,40 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
if (Core::InternalPassportLink(url)) {
return;
}
const auto open = [=] {
const auto openLink = [=] {
UrlClickHandler::Open(url, context.other);
};
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
open();
} else if (!_bot
|| _bot->isVerified()
const auto my = context.other.value<ClickHandlerContext>();
const auto weakController = my.sessionWindow;
const auto controller = weakController.get();
const auto item = controller
? controller->session().data().message(my.itemId)
: nullptr;
const auto media = item ? item->media() : nullptr;
const auto game = media ? media->game() : nullptr;
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
openLink();
}
const auto bot = _bot;
const auto title = game->title;
const auto itemId = my.itemId;
const auto openGame = [=] {
bot->session().attachWebView().showGame({
.bot = bot,
.context = itemId,
.url = url,
.title = title,
});
};
if (_bot->isVerified()
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
open();
openGame();
} else {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto callback = [=, bot = _bot](Fn<void()> close) {
close();
bot->session().local().markBotTrustedOpenGame(bot->id);
open();
openGame();
};
controller->show(Ui::MakeConfirmBox({
.text = tr::lng_allow_bot_pass(

View file

@ -327,21 +327,6 @@ bool ConfirmPhone(
return true;
}
bool ShareGameScore(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto params = url_parse_params(
match->captured(1),
qthelp::UrlParamNameTransform::ToLower);
ShareGameScoreByHash(controller, params.value(u"hash"_q));
controller->window().activate();
return true;
}
bool ApplySocksProxy(
Window::SessionController *controller,
const Match &match,
@ -1230,10 +1215,6 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"^confirmphone/?\\?(.+)(#|$)"_q,
ConfirmPhone
},
{
u"^share_game_score/?\\?(.+)(#|$)"_q,
ShareGameScore
},
{
u"^socks/?\\?(.+)(#|$)"_q,
ApplySocksProxy

View file

@ -1284,7 +1284,14 @@ void CopyPostLink(
not_null<Window::SessionController*> controller,
FullMsgId itemId,
Context context) {
const auto item = controller->session().data().message(itemId);
CopyPostLink(controller->uiShow(), itemId, context);
}
void CopyPostLink(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId,
Context context) {
const auto item = show->session().data().message(itemId);
if (!item || !item->hasDirectLink()) {
return;
}
@ -1311,7 +1318,7 @@ void CopyPostLink(
return channel->hasUsername();
}();
controller->showToast(isPublicLink
show->showToast(isPublicLink
? tr::lng_channel_public_link_copied(tr::now)
: tr::lng_context_about_private_link(tr::now));
}

View file

@ -61,6 +61,10 @@ void CopyPostLink(
not_null<Window::SessionController*> controller,
FullMsgId itemId,
Context context);
void CopyPostLink(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId,
Context context);
void CopyStoryLink(
std::shared_ptr<Main::SessionShow> show,
FullStoryId storyId);

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_blocked_peers.h"
#include "api/api_common.h"
#include "base/qthelp_url.h"
#include "boxes/share_box.h"
#include "core/click_handler_types.h"
#include "data/data_bot_app.h"
#include "data/data_changes.h"
@ -802,6 +803,16 @@ void AttachWebView::botInvokeCustomMethod(
}).send();
}
void AttachWebView::botShareGameScore() {
if (!_panel || !_gameContext) {
return;
} else if (const auto item = _session->data().message(_gameContext)) {
FastShareMessage(uiShow(), item);
} else {
_panel->showToast({ tr::lng_message_not_found(tr::now) });
}
}
void AttachWebView::botClose() {
crl::on_main(this, [=] { cancel(); });
}
@ -1547,6 +1558,7 @@ void AttachWebView::show(
_lastShownUrl = url;
_lastShownQueryId = queryId;
_lastShownButtonText = buttonText;
_gameContext = {};
base::take(_panel);
_catchingCancelInShowCall = true;
_panel = Ui::BotWebView::Show({
@ -1562,6 +1574,24 @@ void AttachWebView::show(
started(queryId);
}
void AttachWebView::showGame(ShowGameParams &&params) {
ActiveWebViews().emplace(this);
base::take(_panel);
_gameContext = params.context;
_catchingCancelInShowCall = true;
_panel = Ui::BotWebView::Show({
.url = params.url,
.storageId = _session->local().resolveStorageIdBots(),
.title = rpl::single(params.title),
.bottom = rpl::single('@' + params.bot->username()),
.delegate = static_cast<Ui::BotWebView::Delegate*>(this),
.menuButtons = Ui::BotWebView::MenuButton::ShareGame,
});
_catchingCancelInShowCall = false;
}
void AttachWebView::started(uint64 queryId) {
Expects(_bot != nullptr);
Expects(_context != nullptr);
@ -1601,6 +1631,57 @@ void AttachWebView::started(uint64 queryId) {
}, _panel->lifetime());
}
std::shared_ptr<Main::SessionShow> AttachWebView::uiShow() {
class Show final : public Main::SessionShow {
public:
explicit Show(not_null<AttachWebView*> that) : _that(that) {
}
void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<Ui::BoxContent>,
std::unique_ptr<Ui::LayerWidget>> &&layer,
Ui::LayerOptions options,
anim::type animated) const override {
using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
using ObjectBox = object_ptr<Ui::BoxContent>;
const auto panel = _that ? _that->_panel.get() : nullptr;
if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
Unexpected("Layers in AttachWebView are not implemented.");
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
if (panel) {
panel->showBox(std::move(*box), options, animated);
}
} else if (panel) {
panel->hideLayer(animated);
}
}
[[nodiscard]] not_null<QWidget*> toastParent() const override {
const auto panel = _that ? _that->_panel.get() : nullptr;
Ensures(panel != nullptr);
return panel->toastParent();
}
[[nodiscard]] bool valid() const override {
return _that && (_that->_panel != nullptr);
}
operator bool() const override {
return valid();
}
[[nodiscard]] Main::Session &session() const override {
Expects(_that.get() != nullptr);
return *_that->_session;
}
private:
const base::weak_ptr<AttachWebView> _that;
};
return std::make_shared<Show>(this);
}
void AttachWebView::showToast(
const QString &text,
Window::SessionController *controller) {

View file

@ -29,6 +29,7 @@ class Panel;
namespace Main {
class Session;
class SessionShow;
} // namespace Main
namespace Window {
@ -155,12 +156,21 @@ public:
[[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);
[[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow();
static void ClearAll();
private:
struct Context;
Webview::ThemeParams botThemeParams() override;
bool botHandleLocalUri(QString uri, bool keepOpen) override;
void botHandleInvoice(QString slug) override;
@ -176,6 +186,7 @@ private:
void botSharePhone(Fn<void(bool shared)> callback) override;
void botInvokeCustomMethod(
Ui::BotWebView::CustomMethodRequest request) override;
void botShareGameScore() override;
void botClose() override;
[[nodiscard]] static Context LookupContext(
@ -271,6 +282,8 @@ private:
rpl::event_stream<> _attachBotsUpdates;
base::flat_set<not_null<UserData*>> _disclaimerAccepted;
FullMsgId _gameContext;
std::unique_ptr<Ui::BotWebView::Panel> _panel;
bool _catchingCancelInShowCall = false;

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "webview/webview_interface.h"
#include "base/debug_log.h"
#include "base/invoke_queued.h"
#include "base/qt_signal_producer.h"
#include "styles/style_payments.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QJsonArray>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <QtGui/QWindow>
namespace Ui::BotWebView {
namespace {
@ -373,6 +375,13 @@ Panel::~Panel() {
void Panel::requestActivate() {
_widget->showAndActivate();
if (const auto widget = _webview ? _webview->window.widget() : nullptr) {
InvokeQueued(widget, [=] {
if (widget->isVisible()) {
_webview->window.focus();
}
});
}
}
void Panel::toggleProgress(bool shown) {
@ -527,9 +536,15 @@ bool Panel::showWebview(
_webview->window.navigate(url);
}
}, &st::menuIconRestore);
callback(tr::lng_bot_terms(tr::now), [=] {
File::OpenUrl(tr::lng_mini_apps_tos_url(tr::now));
}, &st::menuIconGroupLog);
if (_menuButtons & MenuButton::ShareGame) {
callback(tr::lng_iv_share(tr::now), [=] {
_delegate->botShareGameScore();
}, &st::menuIconShare);
} else {
callback(tr::lng_bot_terms(tr::now), [=] {
File::OpenUrl(tr::lng_mini_apps_tos_url(tr::now));
}, &st::menuIconGroupLog);
}
const auto main = (_menuButtons & MenuButton::RemoveFromMainMenu);
if (main || (_menuButtons & MenuButton::RemoveFromMenu)) {
const auto handler = [=] {
@ -691,6 +706,8 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
requestClipboardText(arguments);
} else if (command == "web_app_set_header_color") {
processHeaderColor(arguments);
} else if (command == "share_score") {
_delegate->botShareGameScore();
}
});
@ -722,6 +739,17 @@ postEvent: function(eventType, eventData) {
setupProgressGeometry();
base::qt_signal_producer(
_widget->window()->windowHandle(),
&QWindow::activeChanged
) | rpl::filter([=] {
return _webview && _widget->window()->windowHandle()->isActive();
}) | rpl::start_with_next([=] {
if (_webview && !_webview->window.widget()->isHidden()) {
_webview->window.focus();
}
}, _webview->lifetime);
return true;
}
@ -1207,6 +1235,13 @@ void Panel::updateFooterHeight() {
}
void Panel::showBox(object_ptr<BoxContent> box) {
showBox(std::move(box), LayerOption::KeepOther, anim::type::normal);
}
void Panel::showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) {
if (const auto widget = _webview ? _webview->window.widget() : nullptr) {
const auto hideNow = !widget->isHidden();
if (hideNow || _webview->lastHidingBox) {
@ -1220,10 +1255,12 @@ void Panel::showBox(object_ptr<BoxContent> box) {
&& widget->isHidden()
&& _webview->lastHidingBox == raw) {
widget->show();
_webviewBottom->show();
}
}, _webview->lifetime);
if (hideNow) {
widget->hide();
_webviewBottom->hide();
}
}
}
@ -1237,6 +1274,14 @@ void Panel::showToast(TextWithEntities &&text) {
_widget->showToast(std::move(text));
}
not_null<QWidget*> Panel::toastParent() const {
return _widget->uiShow()->toastParent();
}
void Panel::hideLayer(anim::type animated) {
_widget->hideLayer(animated);
}
void Panel::showCriticalError(const TextWithEntities &text) {
_progress = nullptr;
_webviewProgress = false;

View file

@ -20,6 +20,8 @@ namespace Ui {
class BoxContent;
class RpWidget;
class SeparatePanel;
enum class LayerOption;
using LayerOptions = base::flags<LayerOption>;
} // namespace Ui
namespace Webview {
@ -40,6 +42,7 @@ enum class MenuButton {
OpenBot = 0x01,
RemoveFromMenu = 0x02,
RemoveFromMainMenu = 0x04,
ShareGame = 0x08,
};
inline constexpr bool is_flag_type(MenuButton) { return true; }
using MenuButtons = base::flags<MenuButton>;
@ -67,6 +70,7 @@ public:
virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0;
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0;
virtual void botShareGameScore() = 0;
virtual void botClose() = 0;
};
@ -89,7 +93,13 @@ public:
rpl::producer<QString> bottomText);
void showBox(object_ptr<BoxContent> box);
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated);
void hideLayer(anim::type animated);
void showToast(TextWithEntities &&text);
not_null<QWidget*> toastParent() const;
void showCriticalError(const TextWithEntities &text);
void showWebviewError(
const QString &text,