Implement opening of t.me/bot/app-s.
This commit is contained in:
		
							parent
							
								
									ae5f2add0e
								
							
						
					
					
						commit
						af51307aa6
					
				
					 21 changed files with 358 additions and 44 deletions
				
			
		| 
						 | 
				
			
			@ -444,6 +444,8 @@ PRIVATE
 | 
			
		|||
    data/data_audio_msg_id.h
 | 
			
		||||
    data/data_auto_download.cpp
 | 
			
		||||
    data/data_auto_download.h
 | 
			
		||||
    data/data_bot_app.cpp
 | 
			
		||||
    data/data_bot_app.h
 | 
			
		||||
    data/data_chat.cpp
 | 
			
		||||
    data/data_chat.h
 | 
			
		||||
    data/data_chat_filters.cpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -396,6 +396,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
"lng_username_available" = "This username is available.";
 | 
			
		||||
"lng_username_not_found" = "User @{user} not found.";
 | 
			
		||||
"lng_username_by_phone_not_found" = "User {phone} not found.";
 | 
			
		||||
"lng_username_app_not_found" = "Bot application not found.";
 | 
			
		||||
"lng_username_link" = "This link opens a chat with you:";
 | 
			
		||||
"lng_username_copied" = "Link copied to clipboard.";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1463,6 +1464,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
"lng_action_took_screenshot" = "{from} took a screenshot!";
 | 
			
		||||
"lng_action_you_took_screenshot" = "You took a screenshot!";
 | 
			
		||||
"lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}.";
 | 
			
		||||
"lng_action_bot_allowed_from_app" = "You allowed this bot to message you when you opened {app}.";
 | 
			
		||||
"lng_action_secure_values_sent" = "{user} received the following documents: {documents}";
 | 
			
		||||
"lng_action_secure_personal_details" = "personal details";
 | 
			
		||||
"lng_action_secure_proof_of_identity" = "proof of identity";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,7 +108,11 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
 | 
			
		|||
	};
 | 
			
		||||
	if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)
 | 
			
		||||
		|| url.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
 | 
			
		||||
		open();
 | 
			
		||||
		UrlClickHandler::Open(url, QVariant::fromValue([&] {
 | 
			
		||||
			auto result = context.value<ClickHandlerContext>();
 | 
			
		||||
			result.mayShowConfirmation = !base::IsCtrlPressed();
 | 
			
		||||
			return result;
 | 
			
		||||
		}()));
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto parsedUrl = QUrl::fromUserInput(url);
 | 
			
		||||
		if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,7 @@ struct ClickHandlerContext {
 | 
			
		|||
	Fn<HistoryView::ElementDelegate*()> elementDelegate;
 | 
			
		||||
	base::weak_ptr<Window::SessionController> sessionWindow;
 | 
			
		||||
	std::shared_ptr<Ui::Show> show;
 | 
			
		||||
	bool mayShowConfirmation = false;
 | 
			
		||||
	bool skipBotAutoLogin = false;
 | 
			
		||||
	bool botStartAutoSubmit = false;
 | 
			
		||||
	// Is filled from peer info.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -373,6 +373,8 @@ bool ResolveUsernameOrPhone(
 | 
			
		|||
	if (const auto postId = postParam.toInt()) {
 | 
			
		||||
		post = postId;
 | 
			
		||||
	}
 | 
			
		||||
	const auto appname = params.value(u"appname"_q);
 | 
			
		||||
	const auto appstart = params.value(u"startapp"_q);
 | 
			
		||||
	const auto commentParam = params.value(u"comment"_q);
 | 
			
		||||
	const auto commentId = commentParam.toInt();
 | 
			
		||||
	const auto topicParam = params.value(u"topic"_q);
 | 
			
		||||
| 
						 | 
				
			
			@ -384,6 +386,12 @@ bool ResolveUsernameOrPhone(
 | 
			
		|||
		startToken = gameParam;
 | 
			
		||||
		resolveType = ResolveType::ShareGame;
 | 
			
		||||
	}
 | 
			
		||||
	if (startToken.isEmpty() && params.contains(u"startapp"_q)) {
 | 
			
		||||
		startToken = params.value(u"startapp"_q);
 | 
			
		||||
	}
 | 
			
		||||
	if (!appname.isEmpty()) {
 | 
			
		||||
		resolveType = ResolveType::BotApp;
 | 
			
		||||
	}
 | 
			
		||||
	const auto myContext = context.value<ClickHandlerContext>();
 | 
			
		||||
	using Navigation = Window::SessionNavigation;
 | 
			
		||||
	controller->showPeerByLink(Navigation::PeerByLinkInfo{
 | 
			
		||||
| 
						 | 
				
			
			@ -403,6 +411,8 @@ bool ResolveUsernameOrPhone(
 | 
			
		|||
		.startToken = startToken,
 | 
			
		||||
		.startAdminRights = adminRights,
 | 
			
		||||
		.startAutoSubmit = myContext.botStartAutoSubmit,
 | 
			
		||||
		.botAppName = appname.isEmpty() ? postParam : appname,
 | 
			
		||||
		.botAppForceConfirmation = myContext.mayShowConfirmation,
 | 
			
		||||
		.attachBotUsername = params.value(u"attach"_q),
 | 
			
		||||
		.attachBotToggleCommand = (params.contains(u"startattach"_q)
 | 
			
		||||
			? params.value(u"startattach"_q)
 | 
			
		||||
| 
						 | 
				
			
			@ -1004,6 +1014,7 @@ QString TryConvertUrlToLocal(QString url) {
 | 
			
		|||
			"("
 | 
			
		||||
				"/?\\?|"
 | 
			
		||||
				"/?$|"
 | 
			
		||||
				"/[a-zA-Z0-9\\.\\_]+|"
 | 
			
		||||
				"/\\d+/?(\\?|$)|"
 | 
			
		||||
				"/\\d+/\\d+/?(\\?|$)"
 | 
			
		||||
			")"_q, query, matchOptions)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1014,6 +1025,8 @@ QString TryConvertUrlToLocal(QString url) {
 | 
			
		|||
				added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
 | 
			
		||||
			} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
 | 
			
		||||
				added = u"&post="_q + postMatch->captured(1);
 | 
			
		||||
			} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
 | 
			
		||||
				added = u"&appname="_q + appNameMatch->captured(1);
 | 
			
		||||
			}
 | 
			
		||||
			return base + added + (params.isEmpty() ? QString() : '&' + params);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								Telegram/SourceFiles/data/data_bot_app.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Telegram/SourceFiles/data/data_bot_app.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
/*
 | 
			
		||||
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 "data/data_bot_app.h"
 | 
			
		||||
 | 
			
		||||
BotAppData::BotAppData(not_null<Data::Session*> owner, const BotAppId &id)
 | 
			
		||||
: owner(owner)
 | 
			
		||||
, id(id) {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								Telegram/SourceFiles/data/data_bot_app.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Telegram/SourceFiles/data/data_bot_app.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
/*
 | 
			
		||||
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
 | 
			
		||||
*/
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "data/data_photo.h"
 | 
			
		||||
#include "data/data_document.h"
 | 
			
		||||
 | 
			
		||||
struct BotAppData {
 | 
			
		||||
	BotAppData(not_null<Data::Session*> owner, const BotAppId &id);
 | 
			
		||||
 | 
			
		||||
	const not_null<Data::Session*> owner;
 | 
			
		||||
	BotAppId id = 0;
 | 
			
		||||
	PeerId botId = 0;
 | 
			
		||||
	QString shortName;
 | 
			
		||||
	QString title;
 | 
			
		||||
	QString description;
 | 
			
		||||
	PhotoData *photo = nullptr;
 | 
			
		||||
	DocumentData *document = nullptr;
 | 
			
		||||
 | 
			
		||||
	uint64 accessHash = 0;
 | 
			
		||||
	uint64 hash = 0;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -21,5 +21,4 @@ struct GameData {
 | 
			
		|||
	QString description;
 | 
			
		||||
	PhotoData *photo = nullptr;
 | 
			
		||||
	DocumentData *document = nullptr;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
 | 
			
		||||
#include "data/stickers/data_stickers.h"
 | 
			
		||||
#include "data/notify/data_notify_settings.h"
 | 
			
		||||
#include "data/data_bot_app.h"
 | 
			
		||||
#include "data/data_changes.h"
 | 
			
		||||
#include "data/data_group_call.h"
 | 
			
		||||
#include "data/data_media_types.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -3450,6 +3451,45 @@ void Session::gameApplyFields(
 | 
			
		|||
	notifyGameUpdateDelayed(game);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
not_null<BotAppData*> Session::botApp(BotAppId id) {
 | 
			
		||||
	const auto i = _botApps.find(id);
 | 
			
		||||
	return (i != end(_botApps))
 | 
			
		||||
		? i->second.get()
 | 
			
		||||
		: _botApps.emplace(
 | 
			
		||||
			id,
 | 
			
		||||
			std::make_unique<BotAppData>(this, id)).first->second.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BotAppData *Session::findBotApp(PeerId botId, const QString &appName) const {
 | 
			
		||||
	for (const auto &[id, app] : _botApps) {
 | 
			
		||||
		if (app->botId == botId && app->shortName == appName) {
 | 
			
		||||
			return app.get();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BotAppData *Session::processBotApp(
 | 
			
		||||
		PeerId botId,
 | 
			
		||||
		const MTPBotApp &data) {
 | 
			
		||||
	return data.match([&](const MTPDbotApp &data) {
 | 
			
		||||
		const auto result = botApp(data.vid().v);
 | 
			
		||||
		result->botId = botId;
 | 
			
		||||
		result->shortName = qs(data.vshort_name());
 | 
			
		||||
		result->title = qs(data.vtitle());
 | 
			
		||||
		result->description = qs(data.vdescription());
 | 
			
		||||
		result->photo = processPhoto(data.vphoto());
 | 
			
		||||
		result->document = data.vdocument()
 | 
			
		||||
			? processDocument(*data.vdocument()).get()
 | 
			
		||||
			: nullptr;
 | 
			
		||||
		result->accessHash = data.vaccess_hash().v;
 | 
			
		||||
		result->hash = data.vhash().v;
 | 
			
		||||
		return result.get();
 | 
			
		||||
	}, [](const MTPDbotAppNotModified &) {
 | 
			
		||||
		return (BotAppData*)nullptr;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
not_null<PollData*> Session::poll(PollId id) {
 | 
			
		||||
	auto i = _polls.find(id);
 | 
			
		||||
	if (i == _polls.cend()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -570,6 +570,12 @@ public:
 | 
			
		|||
		not_null<GameData*> original,
 | 
			
		||||
		const MTPGame &data);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] not_null<BotAppData*> botApp(BotAppId id);
 | 
			
		||||
	BotAppData *findBotApp(PeerId botId, const QString &appName) const;
 | 
			
		||||
	BotAppData *processBotApp(
 | 
			
		||||
		PeerId botId,
 | 
			
		||||
		const MTPBotApp &data);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] not_null<PollData*> poll(PollId id);
 | 
			
		||||
	not_null<PollData*> processPoll(const MTPPoll &data);
 | 
			
		||||
	not_null<PollData*> processPoll(const MTPDmessageMediaPoll &data);
 | 
			
		||||
| 
						 | 
				
			
			@ -922,6 +928,9 @@ private:
 | 
			
		|||
	std::unordered_map<
 | 
			
		||||
		GameId,
 | 
			
		||||
		std::unique_ptr<GameData>> _games;
 | 
			
		||||
	std::unordered_map<
 | 
			
		||||
		BotAppId,
 | 
			
		||||
		std::unique_ptr<BotAppData>> _botApps;
 | 
			
		||||
	std::unordered_map<
 | 
			
		||||
		not_null<const GameData*>,
 | 
			
		||||
		base::flat_set<not_null<ViewElement*>>> _gameViews;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -118,6 +118,7 @@ class DocumentData;
 | 
			
		|||
class PhotoData;
 | 
			
		||||
struct WebPageData;
 | 
			
		||||
struct GameData;
 | 
			
		||||
struct BotAppData;
 | 
			
		||||
struct PollData;
 | 
			
		||||
 | 
			
		||||
using PhotoId = uint64;
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +130,7 @@ using GameId = uint64;
 | 
			
		|||
using PollId = uint64;
 | 
			
		||||
using WallPaperId = uint64;
 | 
			
		||||
using CallId = uint64;
 | 
			
		||||
using BotAppId = uint64;
 | 
			
		||||
constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL);
 | 
			
		||||
 | 
			
		||||
struct PreparedPhotoThumb {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1059,6 +1059,12 @@ ServiceAction ParseServiceAction(
 | 
			
		|||
		result.content = content;
 | 
			
		||||
	}, [&](const MTPDmessageActionBotAllowed &data) {
 | 
			
		||||
		auto content = ActionBotAllowed();
 | 
			
		||||
		if (const auto app = data.vapp()) {
 | 
			
		||||
			app->match([&](const MTPDbotApp &data) {
 | 
			
		||||
				content.appId = data.vid().v;
 | 
			
		||||
				content.app = ParseString(data.vtitle());
 | 
			
		||||
			}, [](const MTPDbotAppNotModified &) {});
 | 
			
		||||
		}
 | 
			
		||||
		if (const auto domain = data.vdomain()) {
 | 
			
		||||
			content.domain = ParseString(*domain);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -431,6 +431,8 @@ struct ActionCustomAction {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
struct ActionBotAllowed {
 | 
			
		||||
	uint64 appId = 0;
 | 
			
		||||
	Utf8String app;
 | 
			
		||||
	Utf8String domain;
 | 
			
		||||
	bool attachMenu = false;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1033,6 +1033,9 @@ auto HtmlWriter::Wrap::pushMessage(
 | 
			
		|||
		return data.attachMenu
 | 
			
		||||
			? "You allowed this bot to message you "
 | 
			
		||||
			"when you added it in the attachment menu."_q
 | 
			
		||||
			: data.app.isEmpty()
 | 
			
		||||
			? ("You allowed this bot to message you when you opened "
 | 
			
		||||
				+ SerializeString(data.app))
 | 
			
		||||
			: ("You allowed this bot to message you when you logged in on "
 | 
			
		||||
				+ SerializeString(data.domain));
 | 
			
		||||
	}, [&](const ActionSecureValuesSent &data) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -477,6 +477,10 @@ QByteArray SerializeMessage(
 | 
			
		|||
	}, [&](const ActionBotAllowed &data) {
 | 
			
		||||
		if (data.attachMenu) {
 | 
			
		||||
			pushAction("attach_menu_bot_allowed");
 | 
			
		||||
		} else if (data.appId) {
 | 
			
		||||
			pushAction("allow_sending_messages");
 | 
			
		||||
			push("reason_app_id", data.appId);
 | 
			
		||||
			push("reason_app_name", data.app);
 | 
			
		||||
		} else {
 | 
			
		||||
			pushAction("allow_sending_messages");
 | 
			
		||||
			push("reason_domain", data.domain);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "api/api_updates.h"
 | 
			
		||||
#include "dialogs/ui/dialogs_message_view.h"
 | 
			
		||||
#include "data/notify/data_notify_settings.h"
 | 
			
		||||
#include "data/data_bot_app.h"
 | 
			
		||||
#include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
 | 
			
		||||
#include "data/data_changes.h"
 | 
			
		||||
#include "data/data_session.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -3689,6 +3690,21 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
 | 
			
		|||
			result.text = {
 | 
			
		||||
				tr::lng_action_attach_menu_bot_allowed(tr::now)
 | 
			
		||||
			};
 | 
			
		||||
		} else if (const auto app = action.vapp()) {
 | 
			
		||||
			const auto bot = history()->peer->asUser();
 | 
			
		||||
			const auto botId = bot ? bot->id : PeerId();
 | 
			
		||||
			const auto info = history()->owner().processBotApp(botId, *app);
 | 
			
		||||
			const auto url = (bot && info)
 | 
			
		||||
				? history()->session().createInternalLinkFull(
 | 
			
		||||
					bot->username() + '/' + info->shortName)
 | 
			
		||||
				: QString();
 | 
			
		||||
			result.text = tr::lng_action_bot_allowed_from_app(
 | 
			
		||||
				tr::now,
 | 
			
		||||
				lt_app,
 | 
			
		||||
				(url.isEmpty()
 | 
			
		||||
					? TextWithEntities{ u"App"_q }
 | 
			
		||||
					: Ui::Text::Link(info->title, url)),
 | 
			
		||||
				Ui::Text::WithEntities);
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto domain = qs(action.vdomain().value_or_empty());
 | 
			
		||||
			result.text = tr::lng_action_bot_allowed_from_domain(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "inline_bots/bot_attach_web_view.h"
 | 
			
		||||
 | 
			
		||||
#include "api/api_common.h"
 | 
			
		||||
#include "data/data_bot_app.h"
 | 
			
		||||
#include "data/data_user.h"
 | 
			
		||||
#include "data/data_file_origin.h"
 | 
			
		||||
#include "data/data_document.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -424,6 +425,7 @@ struct AttachWebView::Context {
 | 
			
		|||
	Dialogs::EntryState dialogsEntryState;
 | 
			
		||||
	Api::SendAction action;
 | 
			
		||||
	bool fromSwitch = false;
 | 
			
		||||
	bool fromBotApp = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttachWebView::AttachWebView(not_null<Main::Session*> session)
 | 
			
		||||
| 
						 | 
				
			
			@ -551,13 +553,12 @@ void AttachWebView::request(const WebViewButton &button) {
 | 
			
		|||
			: MTP_inputPeerEmpty())
 | 
			
		||||
	)).done([=](const MTPWebViewResult &result) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		result.match([&](const MTPDwebViewResultUrl &data) {
 | 
			
		||||
		const auto &data = result.data();
 | 
			
		||||
		show(
 | 
			
		||||
			data.vquery_id().v,
 | 
			
		||||
			qs(data.vurl()),
 | 
			
		||||
			button.text,
 | 
			
		||||
			button.fromMenu || button.url.isEmpty());
 | 
			
		||||
		});
 | 
			
		||||
	}).fail([=](const MTP::Error &error) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		if (error.type() == u"BOT_INVALID"_q) {
 | 
			
		||||
| 
						 | 
				
			
			@ -573,7 +574,9 @@ void AttachWebView::cancel() {
 | 
			
		|||
	_panel = nullptr;
 | 
			
		||||
	_context = nullptr;
 | 
			
		||||
	_bot = nullptr;
 | 
			
		||||
	_app = nullptr;
 | 
			
		||||
	_botUsername = QString();
 | 
			
		||||
	_botAppName = QString();
 | 
			
		||||
	_startCommand = QString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -624,9 +627,7 @@ void AttachWebView::requestAddToMenu(
 | 
			
		|||
	Expects(controller != nullptr || _context != nullptr);
 | 
			
		||||
 | 
			
		||||
	if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) {
 | 
			
		||||
		Ui::ShowMultilineToast({
 | 
			
		||||
			.text = { tr::lng_bot_menu_not_supported(tr::now) },
 | 
			
		||||
		});
 | 
			
		||||
		showToast(tr::lng_bot_menu_not_supported(tr::now), controller);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto wasController = (controller != nullptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -694,10 +695,8 @@ void AttachWebView::requestAddToMenu(
 | 
			
		|||
					} else {
 | 
			
		||||
						requestBots();
 | 
			
		||||
						if (!open(types)) {
 | 
			
		||||
							Ui::ShowMultilineToast({
 | 
			
		||||
								.text = {
 | 
			
		||||
									tr::lng_bot_menu_already_added(tr::now) },
 | 
			
		||||
							});
 | 
			
		||||
							showToast(
 | 
			
		||||
								tr::lng_bot_menu_already_added(tr::now));
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -708,17 +707,13 @@ void AttachWebView::requestAddToMenu(
 | 
			
		|||
		_addToMenuBot = nullptr;
 | 
			
		||||
		_addToMenuContext = nullptr;
 | 
			
		||||
		_addToMenuStartCommand = QString();
 | 
			
		||||
		Ui::ShowMultilineToast({
 | 
			
		||||
			.text = { tr::lng_bot_menu_not_supported(tr::now) },
 | 
			
		||||
		});
 | 
			
		||||
		showToast(tr::lng_bot_menu_not_supported(tr::now));
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::removeFromMenu(not_null<UserData*> bot) {
 | 
			
		||||
	toggleInMenu(bot, ToggledState::Removed, [=] {
 | 
			
		||||
		Ui::ShowMultilineToast({
 | 
			
		||||
			.text = { tr::lng_bot_remove_from_menu_done(tr::now) },
 | 
			
		||||
		});
 | 
			
		||||
		showToast(tr::lng_bot_remove_from_menu_done(tr::now));
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -729,9 +724,7 @@ void AttachWebView::resolve() {
 | 
			
		|||
		}
 | 
			
		||||
		_bot = bot->asUser();
 | 
			
		||||
		if (!_bot) {
 | 
			
		||||
			Ui::ShowMultilineToast({
 | 
			
		||||
				.text = { tr::lng_bot_menu_not_supported(tr::now) }
 | 
			
		||||
			});
 | 
			
		||||
			showToast(tr::lng_bot_menu_not_supported(tr::now));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		requestAddToMenu(_bot, _startCommand);
 | 
			
		||||
| 
						 | 
				
			
			@ -760,11 +753,8 @@ void AttachWebView::resolveUsername(
 | 
			
		|||
	}).fail([=](const MTP::Error &error) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		if (error.code() == 400) {
 | 
			
		||||
			Ui::ShowMultilineToast({
 | 
			
		||||
				.text = {
 | 
			
		||||
					tr::lng_username_not_found(tr::now, lt_user, username),
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
			showToast(
 | 
			
		||||
				tr::lng_username_not_found(tr::now, lt_user, username));
 | 
			
		||||
		}
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -838,9 +828,8 @@ void AttachWebView::requestMenu(
 | 
			
		|||
				: MTP_inputPeerEmpty())
 | 
			
		||||
		)).done([=](const MTPWebViewResult &result) {
 | 
			
		||||
			_requestId = 0;
 | 
			
		||||
			result.match([&](const MTPDwebViewResultUrl &data) {
 | 
			
		||||
			const auto &data = result.data();
 | 
			
		||||
			show(data.vquery_id().v, qs(data.vurl()), text);
 | 
			
		||||
			});
 | 
			
		||||
		}).fail([=](const MTP::Error &error) {
 | 
			
		||||
			_requestId = 0;
 | 
			
		||||
			if (error.type() == u"BOT_INVALID"_q) {
 | 
			
		||||
| 
						 | 
				
			
			@ -850,6 +839,129 @@ void AttachWebView::requestMenu(
 | 
			
		|||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::requestApp(
 | 
			
		||||
		not_null<Window::SessionController*> controller,
 | 
			
		||||
		const Api::SendAction &action,
 | 
			
		||||
		not_null<UserData*> bot,
 | 
			
		||||
		const QString &appName,
 | 
			
		||||
		const QString &startParam,
 | 
			
		||||
		bool forceConfirmation) {
 | 
			
		||||
	const auto context = LookupContext(controller, action);
 | 
			
		||||
	if (_requestId
 | 
			
		||||
		&& _bot == bot
 | 
			
		||||
		&& _startCommand == startParam
 | 
			
		||||
		&& _botAppName == appName
 | 
			
		||||
		&& IsSame(_context, context)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	cancel();
 | 
			
		||||
	_bot = bot;
 | 
			
		||||
	_startCommand = startParam;
 | 
			
		||||
	_botAppName = appName;
 | 
			
		||||
	_context = std::make_unique<Context>(context);
 | 
			
		||||
	_context->fromBotApp = true;
 | 
			
		||||
	const auto already = _session->data().findBotApp(_bot->id, appName);
 | 
			
		||||
	_requestId = _session->api().request(MTPmessages_GetBotApp(
 | 
			
		||||
		MTP_inputBotAppShortName(
 | 
			
		||||
			bot->inputUser,
 | 
			
		||||
			MTP_string(appName)),
 | 
			
		||||
		MTP_long(already ? already->hash : 0)
 | 
			
		||||
	)).done([=](const MTPmessages_BotApp &result) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		if (!_bot || !_context) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const auto &data = result.data();
 | 
			
		||||
		const auto firstTime = data.is_inactive();
 | 
			
		||||
		const auto received = _session->data().processBotApp(
 | 
			
		||||
			_bot->id,
 | 
			
		||||
			data.vapp());
 | 
			
		||||
		_app = received ? received : already;
 | 
			
		||||
		if (!_app) {
 | 
			
		||||
			cancel();
 | 
			
		||||
			showToast(tr::lng_username_app_not_found(tr::now));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const auto confirm = firstTime || forceConfirmation;
 | 
			
		||||
		if (confirm) {
 | 
			
		||||
			confirmAppOpen(result.data().is_request_write_access());
 | 
			
		||||
		} else {
 | 
			
		||||
			requestAppView(false);
 | 
			
		||||
		}
 | 
			
		||||
	}).fail([=] {
 | 
			
		||||
		cancel();
 | 
			
		||||
		showToast(tr::lng_username_app_not_found(tr::now));
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::confirmAppOpen(bool requestWriteAccess) {
 | 
			
		||||
	const auto controller = _context ? _context->controller.get() : nullptr;
 | 
			
		||||
	if (!controller || !_bot) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	controller->show(Box([=](not_null<Ui::GenericBox*> box) {
 | 
			
		||||
		const auto allowed = std::make_shared<Ui::Checkbox*>();
 | 
			
		||||
		const auto done = [=](Fn<void()> close) {
 | 
			
		||||
			requestAppView((*allowed) && (*allowed)->checked());
 | 
			
		||||
			close();
 | 
			
		||||
		};
 | 
			
		||||
		Ui::ConfirmBox(box, {
 | 
			
		||||
			tr::lng_allow_bot_webview(
 | 
			
		||||
				tr::now,
 | 
			
		||||
				lt_bot_name,
 | 
			
		||||
				Ui::Text::Bold(_bot->name()),
 | 
			
		||||
				Ui::Text::RichLangValue),
 | 
			
		||||
			done,
 | 
			
		||||
		});
 | 
			
		||||
		if (requestWriteAccess) {
 | 
			
		||||
			(*allowed) = box->addRow(
 | 
			
		||||
				object_ptr<Ui::Checkbox>(
 | 
			
		||||
					box,
 | 
			
		||||
					tr::lng_url_auth_allow_messages(
 | 
			
		||||
						tr::now,
 | 
			
		||||
						lt_bot,
 | 
			
		||||
						Ui::Text::Bold(_bot->name()),
 | 
			
		||||
						Ui::Text::WithEntities),
 | 
			
		||||
					true,
 | 
			
		||||
					st::urlAuthCheckbox),
 | 
			
		||||
				style::margins(
 | 
			
		||||
					st::boxRowPadding.left(),
 | 
			
		||||
					st::boxPhotoCaptionSkip,
 | 
			
		||||
					st::boxRowPadding.right(),
 | 
			
		||||
					st::boxPhotoCaptionSkip));
 | 
			
		||||
			(*allowed)->setAllowTextLines();
 | 
			
		||||
		}
 | 
			
		||||
	}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::requestAppView(bool allowWrite) {
 | 
			
		||||
	if (!_context || !_app) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	using Flag = MTPmessages_RequestAppWebView::Flag;
 | 
			
		||||
	const auto flags = Flag::f_theme_params
 | 
			
		||||
		| (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param)
 | 
			
		||||
		| (allowWrite ? Flag::f_write_allowed : Flag(0));
 | 
			
		||||
	_requestId = _session->api().request(MTPmessages_RequestAppWebView(
 | 
			
		||||
		MTP_flags(flags),
 | 
			
		||||
		_context->action.history->peer->input,
 | 
			
		||||
		MTP_inputBotAppID(MTP_long(_app->id), MTP_long(_app->accessHash)),
 | 
			
		||||
		MTP_string(_startCommand),
 | 
			
		||||
		MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)),
 | 
			
		||||
		MTP_string("tdesktop")
 | 
			
		||||
	)).done([=](const MTPAppWebViewResult &result) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		const auto &data = result.data();
 | 
			
		||||
		const auto queryId = uint64();
 | 
			
		||||
		show(queryId, qs(data.vurl()));
 | 
			
		||||
	}).fail([=](const MTP::Error &error) {
 | 
			
		||||
		_requestId = 0;
 | 
			
		||||
		if (error.type() == u"BOT_INVALID"_q) {
 | 
			
		||||
			requestBots();
 | 
			
		||||
		}
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::confirmOpen(
 | 
			
		||||
		not_null<Window::SessionController*> controller,
 | 
			
		||||
		Fn<void()> done) {
 | 
			
		||||
| 
						 | 
				
			
			@ -895,6 +1007,7 @@ void AttachWebView::show(
 | 
			
		|||
	const auto sendData = crl::guard(this, [=](QByteArray data) {
 | 
			
		||||
		if (!_context
 | 
			
		||||
			|| _context->fromSwitch
 | 
			
		||||
			|| _context->fromBotApp
 | 
			
		||||
			|| _context->action.history->peer != _bot
 | 
			
		||||
			|| queryId) {
 | 
			
		||||
			return;
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,7 +1174,7 @@ void AttachWebView::show(
 | 
			
		|||
void AttachWebView::started(uint64 queryId) {
 | 
			
		||||
	Expects(_bot != nullptr && _context != nullptr);
 | 
			
		||||
 | 
			
		||||
	if (_context->fromSwitch) {
 | 
			
		||||
	if (_context->fromSwitch || !queryId) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1098,6 +1211,24 @@ void AttachWebView::started(uint64 queryId) {
 | 
			
		|||
	}, _panel->lifetime());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::showToast(
 | 
			
		||||
		const QString &text,
 | 
			
		||||
		Window::SessionController *controller) {
 | 
			
		||||
	const auto strong = controller
 | 
			
		||||
		? controller
 | 
			
		||||
		: _context
 | 
			
		||||
		? _context->controller.get()
 | 
			
		||||
		: _addToMenuContext
 | 
			
		||||
		? _addToMenuContext->controller.get()
 | 
			
		||||
		: nullptr;
 | 
			
		||||
	Ui::ShowMultilineToast({
 | 
			
		||||
		.parentOverride = (strong
 | 
			
		||||
			? Window::Show(strong).toastParent().get()
 | 
			
		||||
			: nullptr),
 | 
			
		||||
		.text = { text },
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AttachWebView::confirmAddToMenu(
 | 
			
		||||
		AttachWebViewBot bot,
 | 
			
		||||
		Fn<void()> callback) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1115,9 +1246,7 @@ void AttachWebView::confirmAddToMenu(
 | 
			
		|||
				if (callback) {
 | 
			
		||||
					callback();
 | 
			
		||||
				}
 | 
			
		||||
				Ui::ShowMultilineToast({
 | 
			
		||||
					.text = { tr::lng_bot_add_to_menu_done(tr::now) },
 | 
			
		||||
				});
 | 
			
		||||
				showToast(tr::lng_bot_add_to_menu_done(tr::now));
 | 
			
		||||
			});
 | 
			
		||||
			close();
 | 
			
		||||
		};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,6 +93,13 @@ public:
 | 
			
		|||
	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);
 | 
			
		||||
 | 
			
		||||
	void cancel();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -162,14 +169,22 @@ private:
 | 
			
		|||
	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);
 | 
			
		||||
 | 
			
		||||
	const not_null<Main::Session*> _session;
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Context> _context;
 | 
			
		||||
	UserData *_bot = nullptr;
 | 
			
		||||
	QString _botUsername;
 | 
			
		||||
	QString _botAppName;
 | 
			
		||||
	QString _startCommand;
 | 
			
		||||
	BotAppData *_app = nullptr;
 | 
			
		||||
	QPointer<Ui::GenericBox> _confirmAddBox;
 | 
			
		||||
 | 
			
		||||
	mtpRequestId _requestId = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -447,7 +447,7 @@ void Inner::refreshMosaicOffset() {
 | 
			
		|||
	const auto top = _switchPmButton
 | 
			
		||||
		? (_switchPmButton->height() + st::inlineResultsSkip)
 | 
			
		||||
		: 0;
 | 
			
		||||
	_mosaic.setPadding(st::gifsPadding + QMargins(0, top, 0, 0));
 | 
			
		||||
	_mosaic.setPadding(st::emojiPanMargins + QMargins(0, top, 0, 0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Inner::refreshSwitchPmButton(const CacheEntry *entry) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -357,6 +357,15 @@ void SessionNavigation::showPeerByLinkResolved(
 | 
			
		|||
	using Scope = AddBotToGroupBoxController::Scope;
 | 
			
		||||
	const auto user = peer->asUser();
 | 
			
		||||
	const auto bot = (user && user->isBot()) ? user : nullptr;
 | 
			
		||||
 | 
			
		||||
	// t.me/username/012345 - we thought it was a channel post link, but
 | 
			
		||||
	// after resolving the username we found out it is a bot.
 | 
			
		||||
	const auto resolveType = (bot
 | 
			
		||||
		&& !info.botAppName.isEmpty()
 | 
			
		||||
		&& info.resolveType == ResolveType::Default)
 | 
			
		||||
		? ResolveType::BotApp
 | 
			
		||||
		: info.resolveType;
 | 
			
		||||
 | 
			
		||||
	const auto &replies = info.repliesInfo;
 | 
			
		||||
	if (const auto threadId = std::get_if<ThreadId>(&replies)) {
 | 
			
		||||
		showRepliesForMessage(
 | 
			
		||||
| 
						 | 
				
			
			@ -389,14 +398,29 @@ void SessionNavigation::showPeerByLinkResolved(
 | 
			
		|||
				info.messageId,
 | 
			
		||||
				callback);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (bot && info.resolveType == ResolveType::ShareGame) {
 | 
			
		||||
	} else if (bot && resolveType == ResolveType::BotApp) {
 | 
			
		||||
		const auto itemId = info.clickFromMessageId;
 | 
			
		||||
		const auto item = _session->data().message(itemId);
 | 
			
		||||
		const auto contextPeer = item
 | 
			
		||||
			? item->history()->peer
 | 
			
		||||
			: bot;
 | 
			
		||||
		crl::on_main(this, [=] {
 | 
			
		||||
			bot->session().attachWebView().requestApp(
 | 
			
		||||
				parentController(),
 | 
			
		||||
				Api::SendAction(bot->owner().history(contextPeer)),
 | 
			
		||||
				bot,
 | 
			
		||||
				info.botAppName,
 | 
			
		||||
				info.startToken,
 | 
			
		||||
				info.botAppForceConfirmation);
 | 
			
		||||
		});
 | 
			
		||||
	} else if (bot && resolveType == ResolveType::ShareGame) {
 | 
			
		||||
		Window::ShowShareGameBox(parentController(), bot, info.startToken);
 | 
			
		||||
	} else if (bot
 | 
			
		||||
		&& (info.resolveType == ResolveType::AddToGroup
 | 
			
		||||
			|| info.resolveType == ResolveType::AddToChannel)) {
 | 
			
		||||
		const auto scope = (info.resolveType == ResolveType::AddToGroup)
 | 
			
		||||
		&& (resolveType == ResolveType::AddToGroup
 | 
			
		||||
			|| resolveType == ResolveType::AddToChannel)) {
 | 
			
		||||
		const auto scope = (resolveType == ResolveType::AddToGroup)
 | 
			
		||||
			? (info.startAdminRights ? Scope::GroupAdmin : Scope::All)
 | 
			
		||||
			: (info.resolveType == ResolveType::AddToChannel)
 | 
			
		||||
			: (resolveType == ResolveType::AddToChannel)
 | 
			
		||||
			? Scope::ChannelAdmin
 | 
			
		||||
			: Scope::None;
 | 
			
		||||
		Assert(scope != Scope::None);
 | 
			
		||||
| 
						 | 
				
			
			@ -407,7 +431,7 @@ void SessionNavigation::showPeerByLinkResolved(
 | 
			
		|||
			scope,
 | 
			
		||||
			info.startToken,
 | 
			
		||||
			info.startAdminRights);
 | 
			
		||||
	} else if (info.resolveType == ResolveType::Mention) {
 | 
			
		||||
	} else if (resolveType == ResolveType::Mention) {
 | 
			
		||||
		if (bot || peer->isChannel()) {
 | 
			
		||||
			crl::on_main(this, [=] {
 | 
			
		||||
				showPeerHistory(peer, params);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,6 +97,7 @@ inline constexpr bool is_flag_type(GifPauseReason) { return true; };
 | 
			
		|||
 | 
			
		||||
enum class ResolveType {
 | 
			
		||||
	Default,
 | 
			
		||||
	BotApp,
 | 
			
		||||
	BotStart,
 | 
			
		||||
	AddToGroup,
 | 
			
		||||
	AddToChannel,
 | 
			
		||||
| 
						 | 
				
			
			@ -208,6 +209,8 @@ public:
 | 
			
		|||
		QString startToken;
 | 
			
		||||
		ChatAdminRights startAdminRights;
 | 
			
		||||
		bool startAutoSubmit = false;
 | 
			
		||||
		QString botAppName;
 | 
			
		||||
		bool botAppForceConfirmation = false;
 | 
			
		||||
		QString attachBotUsername;
 | 
			
		||||
		std::optional<QString> attachBotToggleCommand;
 | 
			
		||||
		InlineBots::PeerTypes attachBotChooseTypes;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue