From 344890c5336235ba6bfe500928ec13fca5e2dc60 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 28 Sep 2016 19:23:25 +0300
Subject: [PATCH] Game sharing and inline results supported. Qt patch updated.

Qt patch now disables QT_SCALE_FACTOR and other HighDpi
environment variables reading because tdesktop doesn't support them.
---
 Telegram/Patches/qtbase_5_6_0.diff            |  14 +
 Telegram/Resources/langs/lang.strings         |   7 +
 Telegram/SourceFiles/app.cpp                  |   5 +-
 Telegram/SourceFiles/application.cpp          |  12 +-
 Telegram/SourceFiles/boxes/contactsbox.cpp    | 101 ++++++--
 Telegram/SourceFiles/boxes/contactsbox.h      |   5 +
 Telegram/SourceFiles/boxes/report_box.cpp     |   2 +-
 Telegram/SourceFiles/boxes/sharebox.cpp       |  46 ++--
 Telegram/SourceFiles/boxes/sharebox.h         |   6 +-
 Telegram/SourceFiles/history.cpp              |   8 +
 Telegram/SourceFiles/history.h                |   2 +
 .../history/history_media_types.cpp           |  30 ++-
 .../SourceFiles/history/history_media_types.h |  10 +-
 .../SourceFiles/history/history_message.cpp   |  25 +-
 .../SourceFiles/history/history_message.h     |   4 +
 .../inline_bot_layout_internal.cpp            | 241 +++++++++++++++++-
 .../inline_bots/inline_bot_layout_internal.h  |  36 ++-
 .../inline_bots/inline_bot_layout_item.cpp    |   1 +
 .../inline_bots/inline_bot_result.cpp         |  22 +-
 .../inline_bots/inline_bot_result.h           |   5 +-
 .../inline_bots/inline_bot_send_data.cpp      |   6 +
 .../inline_bots/inline_bot_send_data.h        |  20 ++
 Telegram/SourceFiles/mainwidget.cpp           |  16 +-
 Telegram/SourceFiles/structs.h                |   5 +-
 24 files changed, 546 insertions(+), 83 deletions(-)

diff --git a/Telegram/Patches/qtbase_5_6_0.diff b/Telegram/Patches/qtbase_5_6_0.diff
index e1e270564..6568b94b8 100644
--- a/Telegram/Patches/qtbase_5_6_0.diff
+++ b/Telegram/Patches/qtbase_5_6_0.diff
@@ -47,6 +47,20 @@ index 14e4fd1..c31c62b 100644
      { 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
      { 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
      { 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
+diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
+index b0ef2a2..7d5f7bc 100644
+--- a/src/gui/kernel/qhighdpiscaling.cpp
++++ b/src/gui/kernel/qhighdpiscaling.cpp
+@@ -51,6 +51,9 @@ static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
+ 
+ static inline qreal initialGlobalScaleFactor()
+ {
++    // Disable environment variable dpi scaling changing.
++    // It is not supported by Telegram Desktop :(
++    return 1.;
+ 
+     qreal result = 1;
+     if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) {
 diff --git a/src/gui/kernel/qplatformdialoghelper.h b/src/gui/kernel/qplatformdialoghelper.h
 index 5b2f4ec..346a26f 100644
 --- a/src/gui/kernel/qplatformdialoghelper.h
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 1dc366a17..d36c93ffc 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -580,6 +580,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 "lng_action_pinned_media_contact" = "a contact information";
 "lng_action_pinned_media_location" = "a location mark";
 "lng_action_pinned_media_sticker" = "a sticker";
+"lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker";
+"lng_action_pinned_media_game" = "a game «{game}»";
 "lng_action_game_score" = "{from} scored {count:#|#|#} in {game}";
 
 "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached";
@@ -774,6 +776,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 "lng_bot_groups_not_found" = "No groups found";
 "lng_bot_sure_invite" = "Add the bot to «{group}»?";
 "lng_bot_already_in_group" = "The bot is already a member of the group.";
+"lng_bot_choose_chat" = "Choose Chat";
+"lng_bot_no_chats" = "You have no chats";
+"lng_bot_chats_not_found" = "No chats found";
+"lng_bot_sure_share_game" = "Share the game with {user}?";
+"lng_bot_sure_share_game_group" = "Share the game with «{group}»?";
 
 "lng_typing" = "typing";
 "lng_user_typing" = "{user} is typing";
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 16c3bb700..a5985e0b2 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -1856,8 +1856,9 @@ namespace {
 					gamesData.erase(i);
 				}
 				convert->id = game;
+				convert->accessHash = 0;
 			}
-			if (convert->shortName.isEmpty() && !shortName.isEmpty()) {
+			if (!convert->accessHash && accessHash) {
 				convert->accessHash = accessHash;
 				convert->shortName = textClean(shortName);
 				convert->title = textOneLine(textClean(title));
@@ -1879,7 +1880,7 @@ namespace {
 		} else {
 			result = i.value();
 			if (result != convert) {
-				if (result->shortName.isEmpty() && !shortName.isEmpty()) {
+				if (!result->accessHash && accessHash) {
 					result->accessHash = accessHash;
 					result->shortName = textClean(shortName);
 					result->title = textOneLine(textClean(title));
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index 8a44c4c45..f46009d49 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -650,9 +650,17 @@ namespace Sandbox {
 			cSetScreenScale(dbisTwo);
 		}
 
-		if (application()->devicePixelRatio() > 1) {
+		auto devicePixelRatio = application()->devicePixelRatio();
+		if (devicePixelRatio > 1.) {
+			if ((cPlatform() != dbipMac && cPlatform() != dbipMacOld) || (devicePixelRatio != 2.)) {
+				LOG(("Found non-trivial Device Pixel Ratio: %1").arg(devicePixelRatio));
+				LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(QString::fromLatin1(qgetenv("QT_DEVICE_PIXEL_RATIO"))));
+				LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_SCALE_FACTOR"))));
+				LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR"))));
+				LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(QString::fromLatin1(qgetenv("QT_SCREEN_SCALE_FACTORS"))));
+			}
 			cSetRetina(true);
-			cSetRetinaFactor(application()->devicePixelRatio());
+			cSetRetinaFactor(devicePixelRatio);
 			cSetIntRetinaFactor(int32(cRetinaFactor()));
 			cSetConfigScale(dbisOne);
 			cSetRealScale(dbisOne);
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index 16f02d661..b929f3a54 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -86,6 +86,17 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid
 	init();
 }
 
+template <typename FilterCallback>
+void ContactsInner::addDialogsToList(FilterCallback callback) {
+	auto v = App::main()->dialogsList();
+	for_const (auto row, *v) {
+		auto peer = row->history()->peer;
+		if (callback(peer)) {
+			_contacts->addToEnd(row->history());
+		}
+	}
+}
+
 ContactsInner::ContactsInner(UserData *bot) : TWidget()
 , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
 , _bot(bot)
@@ -93,14 +104,25 @@ ContactsInner::ContactsInner(UserData *bot) : TWidget()
 , _customList(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add))
 , _contacts(_customList.get())
 , _addContactLnk(this, lang(lng_add_contact_button)) {
-	auto v = App::main()->dialogsList();
-	for_const (auto row, *v) {
-		auto peer = row->history()->peer;
-		if (peer->isChat() && peer->asChat()->canEdit()) {
-			_contacts->addToEnd(row->history());
-		} else if (peer->isMegagroup() && (peer->asChannel()->amCreator() || peer->asChannel()->amEditor())) {
-			_contacts->addToEnd(row->history());
-		}
+	if (sharingBotGame()) {
+		addDialogsToList([](PeerData *peer) {
+			if (peer->canWrite()) {
+				if (auto channel = peer->asChannel()) {
+					return !channel->isBroadcast();
+				}
+				return true;
+			}
+			return false;
+		});
+	} else {
+		addDialogsToList([](PeerData *peer) {
+			if (peer->isChat() && peer->asChat()->canEdit()) {
+				return true;
+			} else if (peer->isMegagroup() && (peer->asChannel()->amCreator() || peer->asChannel()->amEditor())) {
+				return true;
+			}
+			return false;
+		});
 	}
 	init();
 }
@@ -166,8 +188,22 @@ void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &old
 }
 
 void ContactsInner::onAddBot() {
-	if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) {
-		MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value<uint64>()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
+	if (auto &info = _bot->botInfo) {
+		if (!info->shareGameShortName.isEmpty()) {
+			MTPmessages_SendMedia::Flags sendFlags = 0;
+
+			auto history = App::historyLoaded(_addToPeer);
+			auto afterRequestId = history ? history->sendRequestId : 0;
+			auto randomId = rand_value<uint64>();
+			auto requestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), _addToPeer->input, MTP_int(0), MTP_inputMediaGame(MTP_inputGameShortName(_bot->inputUser, MTP_string(info->shareGameShortName))), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, afterRequestId);
+			if (history) {
+				history->sendRequestId = requestId;
+			}
+		} else if (!info->startGroupToken.isEmpty()) {
+			MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value<uint64>()), MTP_string(info->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
+		} else {
+			App::main()->addParticipants(_addToPeer, QVector<UserData*>(1, _bot));
+		}
 	} else {
 		App::main()->addParticipants(_addToPeer, QVector<UserData*>(1, _bot));
 	}
@@ -511,7 +547,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 			QString text;
 			int32 skip = 0;
 			if (bot()) {
-				text = lang(cDialogsReceived() ? lng_bot_no_groups : lng_contacts_loading);
+				text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading);
 			} else if (_chat && _membersFilter == MembersFilterAdmins) {
 				text = lang(lng_contacts_loading);
 				p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg);
@@ -538,7 +574,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 			p.setPen(st::noContactsColor->p);
 			QString text;
 			if (bot()) {
-				text = lang(cDialogsReceived() ? lng_bot_groups_not_found : lng_contacts_loading);
+				text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading);
 			} else if (_chat && _membersFilter == MembersFilterAdmins) {
 				text = lang(_chat->participants.isEmpty() ? lng_contacts_loading : lng_contacts_not_found);
 			} else {
@@ -702,11 +738,22 @@ void ContactsInner::chooseParticipant() {
 				connect(_addAdminBox, SIGNAL(confirmed()), this, SLOT(onAddAdmin()));
 				connect(_addAdminBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoAddAdminBox(QObject*)));
 				Ui::showLayer(_addAdminBox, KeepOtherLayers);
+			} else if (sharingBotGame()) {
+				_addToPeer = peer;
+				auto confirmText = [peer] {
+					if (peer->isUser()) {
+						return lng_bot_sure_share_game(lt_user, App::peerName(peer));
+					}
+					return lng_bot_sure_share_game_group(lt_group, peer->name);
+				};
+				auto box = std_::make_unique<ConfirmBox>(confirmText());
+				connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot()));
+				Ui::showLayer(box.release(), KeepOtherLayers);
 			} else if (bot() && (peer->isChat() || peer->isMegagroup())) {
 				_addToPeer = peer;
-				ConfirmBox *box = new ConfirmBox(lng_bot_sure_invite(lt_group, peer->name));
-				connect(box, SIGNAL(confirmed()), this, SLOT(onAddBot()));
-				Ui::showLayer(box, KeepOtherLayers);
+				auto box = std_::make_unique<ConfirmBox>(lng_bot_sure_invite(lt_group, peer->name));
+				connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot()));
+				Ui::showLayer(box.release(), KeepOtherLayers);
 			} else {
 				Ui::hideSettingsAndLayer(true);
 				App::main()->choosePeer(peer->id, ShowAtUnreadMsgId);
@@ -899,7 +946,7 @@ void ContactsInner::updateFilter(QString filter) {
 			_mouseSel = false;
 			refresh();
 
-			if (!bot() && (!_chat || _membersFilter != MembersFilterAdmins)) {
+			if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilterAdmins)) {
 				_searching = true;
 				emit searchByUsername();
 			}
@@ -966,6 +1013,15 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPPeer>
 				} else {
 					continue; // skip
 				}
+			} else if (sharingBotGame()) {
+				if (!p->canWrite()) {
+					continue;
+				}
+				if (auto channel = p->asChannel()) {
+					if (channel->isBroadcast()) {
+						continue;
+					}
+				}
 			}
 
 			ContactData *d = new ContactData();
@@ -1032,6 +1088,10 @@ UserData *ContactsInner::bot() const {
 	return _bot;
 }
 
+bool ContactsInner::sharingBotGame() const {
+	return (_bot && _bot->botInfo) ? !_bot->botInfo->shareGameShortName.isEmpty() : false;
+}
+
 CreatingGroupType ContactsInner::creating() const {
 	return _creating;
 }
@@ -1040,8 +1100,11 @@ ContactsInner::~ContactsInner() {
 	for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
 		delete *i;
 	}
-	if (_bot || (_chat && _membersFilter == MembersFilterAdmins)) {
-		if (_bot && _bot->botInfo) _bot->botInfo->startGroupToken = QString();
+	if (_bot) {
+		if (auto &info = _bot->botInfo) {
+			info->startGroupToken = QString();
+			info->shareGameShortName = QString();
+		}
 	}
 }
 
@@ -1499,6 +1562,8 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
 		QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant));
 		QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax()));
 		paintTitle(p, title, additional);
+	} else if (_inner.sharingBotGame()) {
+		paintTitle(p, lang(lng_bot_choose_chat));
 	} else if (_inner.bot()) {
 		paintTitle(p, lang(lng_bot_choose_group));
 	} else {
diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h
index 27a13294a..1d3976e6d 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.h
+++ b/Telegram/SourceFiles/boxes/contactsbox.h
@@ -78,6 +78,8 @@ public:
 	UserData *bot() const;
 	CreatingGroupType creating() const;
 
+	bool sharingBotGame() const;
+
 	int32 selectedCount() const;
 	bool hasAlreadyMembersInChannel() const {
 		return !_already.isEmpty();
@@ -121,6 +123,9 @@ private:
 	void addAdminDone(const MTPUpdates &result, mtpRequestId req);
 	bool addAdminFail(const RPCError &error, mtpRequestId req);
 
+	template <typename FilterCallback>
+	void addDialogsToList(FilterCallback callback);
+
 	int32 _rowHeight;
 	int _newItemHeight = 0;
 	bool _newItemSel = false;
diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp
index d8ec67ce7..e845390cc 100644
--- a/Telegram/SourceFiles/boxes/report_box.cpp
+++ b/Telegram/SourceFiles/boxes/report_box.cpp
@@ -87,7 +87,7 @@ void ReportBox::onChange() {
 		_reasonOtherText.destroy();
 		updateMaxHeight();
 	}
-	if (App::wnd()) App::wnd()->setInnerFocus();
+	_reasonOtherText->setFocus();
 }
 
 void ReportBox::doSetInnerFocus() {
diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp
index 71b2d3e3a..bbf3a38dc 100644
--- a/Telegram/SourceFiles/boxes/sharebox.cpp
+++ b/Telegram/SourceFiles/boxes/sharebox.cpp
@@ -32,11 +32,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "boxes/confirmbox.h"
 #include "apiwrap.h"
 #include "ui/toast/toast.h"
+#include "history/history_media_types.h"
 
-ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback) : ItemListBox(st::boxScroll)
+ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll)
 , _copyCallback(std_::move(copyCallback))
 , _submitCallback(std_::move(submitCallback))
-, _inner(this)
+, _inner(this, std_::move(filterCallback))
 , _filter(this, st::boxSearchField, lang(lng_participant_filter))
 , _filterCancel(this, st::boxSearchCancel)
 , _copy(this, lang(lng_share_copy_link), st::defaultBoxButton)
@@ -241,7 +242,8 @@ void ShareBox::onScroll() {
 
 namespace internal {
 
-ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent)
+ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
+, _filterCallback(std_::move(filterCallback))
 , _chatsIndexed(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add)) {
 	_rowsTop = st::shareRowsTop;
 	_rowHeight = st::shareRowHeight;
@@ -250,7 +252,7 @@ ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent)
 	auto dialogs = App::main()->dialogsList();
 	for_const (auto row, dialogs->all()) {
 		auto history = row->history();
-		if (history->peer->canWrite()) {
+		if (_filterCallback(history->peer)) {
 			_chatsIndexed->addToEnd(history);
 		}
 	}
@@ -863,7 +865,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
 		}
 		if (j == already) {
 			auto *peer = App::peer(peerId);
-			if (!peer || !peer->canWrite()) continue;
+			if (!peer || !_filterCallback(peer)) continue;
 
 			auto chat = new Chat(peer);
 			updateChatName(chat, peer);
@@ -958,24 +960,15 @@ void shareGameScoreFromItem(HistoryItem *item) {
 		if (auto main = App::main()) {
 			if (auto item = App::histItemById(data->msgId)) {
 				if (auto bot = item->getMessageBot()) {
-					if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
-						for (int i = 0, rowsCount = markup->rows.size(); i != rowsCount; ++i) {
-							auto &row = markup->rows[i];
-							for (int j = 0, buttonsCount = row.size(); j != buttonsCount; ++j) {
-								auto &button = row[j];
-								if (button.type == HistoryMessageReplyMarkup::Button::Type::Game) {
-									auto strData = QString::fromUtf8(button.data);
-									auto parts = strData.split(',');
-									t_assert(parts.size() > 1);
+					if (auto media = item->getMedia()) {
+						if (media->type() == MediaTypeGame) {
+							auto shortName = static_cast<HistoryGame*>(media)->game()->shortName;
 
-									QApplication::clipboard()->setText(qsl("https://telegram.me/") + bot->username + qsl("?start=") + parts[1]);
+							QApplication::clipboard()->setText(qsl("https://telegram.me/") + bot->username + qsl("?game=") + shortName);
 
-									Ui::Toast::Config toast;
-									toast.text = lang(lng_share_game_link_copied);
-									Ui::Toast::Show(App::wnd(), toast);
-									return;
-								}
-							}
+							Ui::Toast::Config toast;
+							toast.text = lang(lng_share_game_link_copied);
+							Ui::Toast::Show(App::wnd(), toast);
 						}
 					}
 				}
@@ -1015,7 +1008,16 @@ void shareGameScoreFromItem(HistoryItem *item) {
 			}
 		}
 	};
-	Ui::showLayer(new ShareBox(std_::move(copyCallback), std_::move(submitCallback)));
+	auto filterCallback = [](PeerData *peer) {
+		if (peer->canWrite()) {
+			if (auto channel = peer->asChannel()) {
+				return !channel->isBroadcast();
+			}
+			return true;
+		}
+		return false;
+	};
+	Ui::showLayer(new ShareBox(std_::move(copyCallback), std_::move(submitCallback), std_::move(filterCallback)));
 }
 
 } // namespace
diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h
index 11288b6d8..7f7d8e99d 100644
--- a/Telegram/SourceFiles/boxes/sharebox.h
+++ b/Telegram/SourceFiles/boxes/sharebox.h
@@ -47,7 +47,8 @@ class ShareBox : public ItemListBox, public RPCSender {
 public:
 	using CopyCallback = base::lambda_unique<void()>;
 	using SubmitCallback = base::lambda_unique<void(const QVector<PeerData*> &)>;
-	ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback);
+	using FilterCallback = base::lambda_unique<bool(PeerData*)>;
+	ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback);
 
 private slots:
 	void onFilterUpdate();
@@ -112,7 +113,7 @@ class ShareInner : public ScrolledWidget, public RPCSender, private base::Subscr
 	Q_OBJECT
 
 public:
-	ShareInner(QWidget *parent);
+	ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
 
 	QVector<PeerData*> selected() const;
 	bool hasSelected() const;
@@ -197,6 +198,7 @@ private:
 	int _active = -1;
 	int _upon = -1;
 
+	ShareBox::FilterCallback _filterCallback;
 	std_::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
 	QString _filter;
 	using FilteredDialogs = QVector<Dialogs::Row*>;
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 56b7b974e..5a2f73667 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -881,6 +881,10 @@ HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32
 	return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, photo, caption, markup);
 }
 
+HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
+	return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, game, markup);
+}
+
 HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
 	return addNewItem(HistoryService::create(this, msgId, date, text, flags), newMsg);
 }
@@ -928,6 +932,10 @@ HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaB
 	return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, photo, caption, markup), true);
 }
 
+HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
+	return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, game, markup), true);
+}
+
 bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method) {
 	bool adding = false;
 	switch (method) {
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 02af01452..c9d44ebfb 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -205,6 +205,7 @@ public:
 	HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *item);
 	HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
 	HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
+	HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup);
 
 	void addOlderSlice(const QVector<MTPMessage> &slice);
 	void addNewerSlice(const QVector<MTPMessage> &slice);
@@ -463,6 +464,7 @@ protected:
 	HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg);
 	HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
 	HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
+	HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup);
 
 	HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
 	HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp
index d2e371f0f..5cb96d971 100644
--- a/Telegram/SourceFiles/history/history_media_types.cpp
+++ b/Telegram/SourceFiles/history/history_media_types.cpp
@@ -69,7 +69,7 @@ inline void initTextOptions() {
 
 bool needReSetInlineResultDocument(const MTPMessageMedia &media, DocumentData *existing) {
 	if (media.type() == mtpc_messageMediaDocument) {
-		if (DocumentData *document = App::feedDocument(media.c_messageMediaDocument().vdocument)) {
+		if (auto document = App::feedDocument(media.c_messageMediaDocument().vdocument)) {
 			if (document == existing) {
 				return false;
 			} else {
@@ -1964,8 +1964,6 @@ private:
 } // namespace
 
 HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : HistoryMedia(parent)
-, _pixw(1)
-, _pixh(1)
 , _data(document)
 , _emoji(_data->sticker()->alt) {
 	_data->thumb->load();
@@ -3103,11 +3101,12 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, uint
 		p.translate(attachLeft, attachTop);
 		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
 		auto pixwidth = _attach->currentWidth();
+		auto pixheight = _attach->height();
 
-		auto gameX = st::msgDateImgDelta;
-		auto gameY = st::msgDateImgDelta;
 		auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x();
 		auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
+		auto gameX = pixwidth - st::msgDateImgDelta - gameW;
+		auto gameY = pixheight - st::msgDateImgDelta - gameH;
 
 		App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
 
@@ -3214,12 +3213,25 @@ void HistoryGame::detachFromParent() {
 	if (_attach) _attach->detachFromParent();
 }
 
-QString HistoryGame::notificationText() const {
-	return _data->title;
+void HistoryGame::updateSentMedia(const MTPMessageMedia &media) {
+	if (media.type() == mtpc_messageMediaGame) {
+		auto &game = media.c_messageMediaGame().vgame;
+		if (game.type() == mtpc_game) {
+			App::feedGame(game.c_game(), _data);
+		}
+	}
 }
 
-QString HistoryGame::inDialogsText() const {
-	return textcmdLink(1, _data->title);
+bool HistoryGame::needReSetInlineResultMedia(const MTPMessageMedia &media) {
+	updateSentMedia(media);
+	return false;
+}
+
+QString HistoryGame::notificationText() const {
+	QString result; // add a game controller emoji before game title
+	result.reserve(_data->title.size() + 3);
+	result.append(QChar(0xD83C)).append(QChar(0xDFAE)).append(QChar(' ')).append(_data->title);
+	return result;
 }
 
 TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h
index 2d457231c..fe62bcb08 100644
--- a/Telegram/SourceFiles/history/history_media_types.h
+++ b/Telegram/SourceFiles/history/history_media_types.h
@@ -552,6 +552,9 @@ public:
 	bool customInfoLayout() const override {
 		return true;
 	}
+	QString emoji() const {
+		return _emoji;
+	}
 
 private:
 	int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
@@ -560,7 +563,8 @@ private:
 	}
 	QString toString() const;
 
-	int16 _pixw, _pixh;
+	int16 _pixw = 1;
+	int16 _pixh = 1;
 	ClickHandlerPtr _packLink;
 	DocumentData *_data;
 	QString _emoji;
@@ -762,7 +766,6 @@ public:
 	}
 
 	QString notificationText() const override;
-	QString inDialogsText() const override;
 	TextWithEntities selectedText(TextSelection selection) const override;
 
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
@@ -793,6 +796,9 @@ public:
 		return _data;
 	}
 
+	void updateSentMedia(const MTPMessageMedia &media) override;
+	bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
+
 	bool needsBubble() const override {
 		return true;
 	}
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index 99def6c55..cf0894118 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -49,7 +49,7 @@ MediaOverviewType messageMediaToOverviewType(HistoryMedia *media) {
 	case MediaTypePhoto: return OverviewPhotos;
 	case MediaTypeVideo: return OverviewVideos;
 	case MediaTypeFile: return OverviewFiles;
-	case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewFiles;
+	case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewCount;
 	case MediaTypeVoiceFile: return OverviewVoiceFiles;
 	case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewFiles;
 	default: break;
@@ -465,6 +465,14 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
 	setText(TextWithEntities());
 }
 
+HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup)
+	: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
+	createComponentsHelper(flags, replyTo, viaBotId, markup);
+
+	_media.reset(new HistoryGame(this, game));
+	setText(TextWithEntities());
+}
+
 void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup) {
 	CreateConfig config;
 
@@ -1899,7 +1907,7 @@ bool HistoryService::preparePinnedText(const QString &from, QString *outText, Li
 	ClickHandlerPtr second;
 	auto pinned = Get<HistoryServicePinned>();
 	if (pinned && pinned->msg) {
-		HistoryMedia *media = pinned->msg->getMedia();
+		auto media = pinned->msg->getMedia();
 		QString mediaText;
 		switch (media ? media->type() : MediaTypeCount) {
 		case MediaTypePhoto: mediaText = lang(lng_action_pinned_media_photo); break;
@@ -1907,10 +1915,21 @@ bool HistoryService::preparePinnedText(const QString &from, QString *outText, Li
 		case MediaTypeContact: mediaText = lang(lng_action_pinned_media_contact); break;
 		case MediaTypeFile: mediaText = lang(lng_action_pinned_media_file); break;
 		case MediaTypeGif: mediaText = lang(lng_action_pinned_media_gif); break;
-		case MediaTypeSticker: mediaText = lang(lng_action_pinned_media_sticker); break;
+		case MediaTypeSticker: {
+			auto emoji = static_cast<HistorySticker*>(media)->emoji();
+			if (emoji.isEmpty()) {
+				mediaText = lang(lng_action_pinned_media_sticker);
+			} else {
+				mediaText = lng_action_pinned_media_emoji_sticker(lt_emoji, emoji);
+			}
+		} break;
 		case MediaTypeLocation: mediaText = lang(lng_action_pinned_media_location); break;
 		case MediaTypeMusicFile: mediaText = lang(lng_action_pinned_media_audio); break;
 		case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break;
+		case MediaTypeGame: {
+			auto title = static_cast<HistoryGame*>(media)->game()->title;
+			mediaText = lng_action_pinned_media_game(lt_game, title);
+		} break;
 		}
 		if (mediaText.isEmpty()) {
 			QString original = pinned->msg->originalText().text;
diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h
index 1bcd8c4e5..bfefaec4c 100644
--- a/Telegram/SourceFiles/history/history_message.h
+++ b/Telegram/SourceFiles/history/history_message.h
@@ -39,6 +39,9 @@ public:
 	static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
 		return _create(history, msgId, flags, replyTo, viaBotId, date, from, photo, caption, markup);
 	}
+	static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
+		return _create(history, msgId, flags, replyTo, viaBotId, date, from, game, markup);
+	}
 
 	void initTime();
 	void initMedia(const MTPMessageMedia *media);
@@ -164,6 +167,7 @@ private:
 	HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message
 	HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
 	HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
+	HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup); // local game
 	friend class HistoryItemInstantiated<HistoryMessage>;
 
 	void setEmptyText();
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
index 63385e707..fac4a6cff 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
@@ -134,7 +134,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
 
 	bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
 	if (loaded && !gif() && _gif != Media::Clip::BadReader) {
-		Gif *that = const_cast<Gif*>(this);
+		auto that = const_cast<Gif*>(this);
 		that->_gif = new Media::Clip::Reader(document->location(), document->data(), [that](Media::Clip::Notification notification) {
 			that->clipCallback(notification);
 		});
@@ -276,7 +276,6 @@ QSize Gif::countFrameSize() const {
 
 Gif::~Gif() {
 	if (gif()) deleteAndMark(_gif);
-	deleteAndMark(_animation);
 }
 
 void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const {
@@ -306,7 +305,7 @@ void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const {
 
 void Gif::ensureAnimation() const {
 	if (!_animation) {
-		_animation = new AnimationData(animation(const_cast<Gif*>(this), &Gif::step_radial));
+		_animation = std_::make_unique<AnimationData>(animation(const_cast<Gif*>(this), &Gif::step_radial));
 	}
 }
 
@@ -324,8 +323,7 @@ void Gif::step_radial(uint64 ms, bool timer) {
 		DocumentData *document = getShownDocument();
 		_animation->radial.update(document->progress(), !document->loading() || document->loaded(), ms);
 		if (!_animation->radial.animating() && document->loaded()) {
-			delete _animation;
-			_animation = nullptr;
+			_animation.reset();
 		}
 	}
 }
@@ -1140,6 +1138,239 @@ void Article::prepareThumb(int width, int height) const {
 	}
 }
 
+Game::Game(Result *result) : ItemBase(result)
+, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
+, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
+	countFrameSize();
+}
+
+void Game::countFrameSize() {
+	if (auto document = getResultDocument()) {
+		if (document->isAnimation()) {
+			auto documentSize = document->dimensions;
+			if (documentSize.isEmpty()) {
+				documentSize = QSize(st::inlineThumbSize, st::inlineThumbSize);
+			}
+			auto resizeByHeight1 = (documentSize.width() > documentSize.height()) && (documentSize.height() >= st::inlineThumbSize);
+			auto resizeByHeight2 = (documentSize.height() >= documentSize.width()) && (documentSize.width() < st::inlineThumbSize);
+			if (resizeByHeight1 || resizeByHeight2) {
+				if (documentSize.height() > st::inlineThumbSize) {
+					_frameSize = QSize((documentSize.width() * st::inlineThumbSize) / documentSize.height(), st::inlineThumbSize);
+				}
+			} else {
+				if (documentSize.width() > st::inlineThumbSize) {
+					_frameSize = QSize(st::inlineThumbSize, (documentSize.height() * st::inlineThumbSize) / documentSize.width());
+				}
+			}
+			if (!_frameSize.width()) {
+				_frameSize.setWidth(1);
+			}
+			if (!_frameSize.height()) {
+				_frameSize.setHeight(1);
+			}
+		}
+	}
+}
+
+void Game::initDimensions() {
+	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
+	int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
+	TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
+	_title.setText(st::semiboldFont, textOneLine(_result->getLayoutTitle()), titleOpts);
+	int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
+
+	int32 descriptionLines = 2;
+	QString description = _result->getLayoutDescription();
+	TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
+	_description.setText(st::normalFont, description, descriptionOpts);
+	int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height);
+
+	_minh = titleHeight + descriptionHeight;
+	accumulate_max(_minh, st::inlineThumbSize);
+	_minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
+}
+
+void Game::setPosition(int32 position) {
+	ItemBase::setPosition(position);
+	if (_position < 0) {
+		if (gif()) delete _gif;
+		_gif = 0;
+	}
+}
+
+void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
+	int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft;
+
+	left = st::inlineThumbSize + st::inlineThumbSkip;
+	auto rthumb = rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width);
+
+	// Gif thumb
+	auto thumbDisplayed = false, radial = false;
+	auto document = getResultDocument();
+	auto animatedThumb = document && document->isAnimation();
+	if (animatedThumb) {
+		document->automaticLoad(nullptr);
+
+		bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
+		if (loaded && !gif() && _gif != Media::Clip::BadReader) {
+			auto that = const_cast<Game*>(this);
+			that->_gif = new Media::Clip::Reader(document->location(), document->data(), [that](Media::Clip::Notification notification) {
+				that->clipCallback(notification);
+			});
+			if (gif()) _gif->setAutoplay();
+		}
+
+		bool animating = (gif() && _gif->started());
+		if (displayLoading) {
+			if (!_radial) {
+				_radial = std_::make_unique<Ui::RadialAnimation>(animation(const_cast<Game*>(this), &Game::step_radial));
+			}
+			if (!_radial->animating()) {
+				_radial->start(document->progress());
+			}
+		}
+		radial = isRadialAnimation(context->ms);
+
+		if (animating) {
+			if (!_thumb.isNull()) _thumb = QPixmap();
+			auto animationThumb = _gif->current(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, context->paused ? 0 : context->ms);
+			p.drawPixmapLeft(rthumb.topLeft(), _width, animationThumb);
+			thumbDisplayed = true;
+		}
+	}
+
+	if (!thumbDisplayed) {
+		prepareThumb(st::inlineThumbSize, st::inlineThumbSize);
+		if (_thumb.isNull()) {
+			p.fillRect(rthumb, st::overviewPhotoBg);
+		} else {
+			p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
+		}
+	}
+
+	if (radial) {
+		p.fillRect(rthumb, st::msgDateImgBg);
+		QRect inner((st::inlineThumbSize - st::msgFileSize) / 2, (st::inlineThumbSize - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
+		if (radial) {
+			p.setOpacity(1);
+			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+			_radial->draw(p, rinner, st::msgFileRadialLine, st::msgInBg);
+		}
+	}
+
+	p.setPen(st::black);
+	_title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
+	int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
+
+	p.setPen(st::inlineDescriptionFg);
+	int32 descriptionLines = 2;
+	_description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
+
+	if (!context->lastRow) {
+		p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
+	}
+}
+
+void Game::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+	int left = st::inlineThumbSize + st::inlineThumbSkip;
+	if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
+		link = _send;
+		return;
+	}
+	if (x >= left && x < _width && y >= 0 && y < _height) {
+		link = _send;
+		return;
+	}
+}
+
+void Game::prepareThumb(int width, int height) const {
+	auto thumb = ([this]() {
+		if (auto photo = getResultPhoto()) {
+			return photo->medium;
+		} else if (auto document = getResultDocument()) {
+			return document->thumb;
+		}
+		return ImagePtr();
+	})();
+	if (thumb->isNull()) {
+		return;
+	}
+
+	if (thumb->loaded()) {
+		if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
+			int w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1);
+			auto resizeByHeight1 = (w * height > h * width) && (h >= height);
+			auto resizeByHeight2 = (h * width >= w * height) && (w < width);
+			if (resizeByHeight1 || resizeByHeight2) {
+				if (h > height) {
+					w = w * height / h;
+					h = height;
+				}
+			} else {
+				if (w > width) {
+					h = h * width / w;
+					w = width;
+				}
+			}
+			_thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height);
+		}
+	} else {
+		thumb->load();
+	}
+}
+
+bool Game::isRadialAnimation(uint64 ms) const {
+	if (!_radial || !_radial->animating()) return false;
+
+	_radial->step(ms);
+	return _radial && _radial->animating();
+}
+
+void Game::step_radial(uint64 ms, bool timer) {
+	if (timer) {
+		update();
+	} else {
+		auto document = getResultDocument();
+		_radial->update(document->progress(), !document->loading() || document->loaded(), ms);
+		if (!_radial->animating() && document->loaded()) {
+			_radial.reset();
+		}
+	}
+}
+
+void Game::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
+	switch (notification) {
+	case NotificationReinit: {
+		if (gif()) {
+			if (_gif->state() == State::Error) {
+				delete _gif;
+				_gif = BadReader;
+				getResultDocument()->forget();
+			} else if (_gif->ready() && !_gif->started()) {
+				_gif->start(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None);
+			} else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) {
+				delete _gif;
+				_gif = nullptr;
+				getResultDocument()->forget();
+			}
+		}
+
+		update();
+	} break;
+
+	case NotificationRepaint: {
+		if (gif() && !_gif->currentDisplayed()) {
+			update();
+		}
+	} break;
+	}
+}
+
+Game::~Game() {
+	if (gif()) deleteAndMark(_gif);
+}
+
 } // namespace internal
 } // namespace Layout
 } // namespace InlineBots
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
index d151cb86b..d9327d9d5 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
@@ -116,7 +116,7 @@ private:
 		FloatAnimation _a_over;
 		Ui::RadialAnimation radial;
 	};
-	mutable AnimationData *_animation = nullptr;
+	mutable std_::unique_ptr<AnimationData> _animation;
 	mutable FloatAnimation _a_deleteOver;
 
 };
@@ -339,6 +339,40 @@ private:
 
 };
 
+class Game : public ItemBase {
+public:
+	Game(Result *result);
+
+	void setPosition(int32 position) override;
+	void initDimensions() override;
+
+	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+
+	~Game();
+
+private:
+	void countFrameSize();
+
+	bool gif() const {
+		return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
+	}
+	void prepareThumb(int32 width, int32 height) const;
+
+	bool isRadialAnimation(uint64 ms) const;
+	void step_radial(uint64 ms, bool timer);
+
+	void clipCallback(Media::Clip::Notification notification);
+
+	Media::Clip::Reader *_gif = nullptr;
+	mutable QPixmap _thumb;
+	mutable std_::unique_ptr<Ui::RadialAnimation> _radial;
+	Text _title, _description;
+
+	QSize _frameSize;
+
+};
+
 } // namespace internal
 } // namespace Layout
 } // namespace InlineBots
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
index af03a20f0..bceb885b1 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
@@ -113,6 +113,7 @@ std_::unique_ptr<ItemBase> ItemBase::createLayout(Result *result, bool forceThum
 	case Type::Article:
 	case Type::Geo:
 	case Type::Venue: return std_::make_unique<internal::Article>(result, forceThumb); break;
+	case Type::Game: return std_::make_unique<internal::Game>(result); break;
 	case Type::Contact: return std_::make_unique<internal::Contact>(result); break;
 	}
 	return std_::unique_ptr<ItemBase>();
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
index d3afc27b4..ab9824bb3 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
@@ -47,6 +47,7 @@ std_::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 		result->insert(qsl("contact"), Result::Type::Contact);
 		result->insert(qsl("venue"), Result::Type::Venue);
 		result->insert(qsl("geo"), Result::Type::Geo);
+		result->insert(qsl("game"), Result::Type::Game);
 		return result.release();
 	})() };
 
@@ -122,6 +123,9 @@ std_::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 		if (result->_type == Type::Photo) {
 			result->createPhoto();
 			result->sendData.reset(new internal::SendPhoto(result->_photo, qs(r.vcaption)));
+		} else if (result->_type == Type::Game) {
+			result->createGame();
+			result->sendData.reset(new internal::SendGame(result->_game));
 		} else {
 			result->createDocument();
 			result->sendData.reset(new internal::SendFile(result->_document, qs(r.vcaption)));
@@ -313,7 +317,7 @@ void Result::createPhoto() {
 	ImagePtr medium = ImagePtr(mediumsize.width(), mediumsize.height());
 
 	ImagePtr full = ImagePtr(_content_url, _width, _height);
-	uint64 photoId = rand_value<uint64>();
+	auto photoId = rand_value<PhotoId>();
 	_photo = App::photoSet(photoId, 0, 0, unixtime(), _thumb, medium, full);
 	_photo->thumb = _thumb;
 }
@@ -321,8 +325,6 @@ void Result::createPhoto() {
 void Result::createDocument() {
 	if (_document) return;
 
-	uint64 docId = rand_value<uint64>();
-
 	if (!_thumb_url.isEmpty()) {
 		_thumb = ImagePtr(_thumb_url, QSize(90, 90));
 	}
@@ -352,16 +354,16 @@ void Result::createDocument() {
 		attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(_duration), MTPstring(), MTPstring(), MTPbytes()));
 	}
 
-	MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
-
-	_document = App::feedDocument(document);
+	auto documentId = rand_value<DocumentId>();
+	_document = App::documentSet(documentId, nullptr, 0, 0, unixtime(), attributes, mime, _thumb, MTP::maindc(), 0, StorageImageLocation());
 	_document->setContentUrl(_content_url);
-	if (!_thumb->isNull()) {
-		_document->thumb = _thumb;
-	}
 }
 
-Result::~Result() {
+void Result::createGame() {
+	if (_game) return;
+
+	auto gameId = rand_value<GameId>();
+	_game = App::gameSet(gameId, nullptr, 0, QString(), _title, _description, _photo, _document);
 }
 
 } // namespace InlineBots
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h
index 105b50b46..09c58aad9 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_result.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h
@@ -74,11 +74,10 @@ public:
 	QString getLayoutTitle() const;
 	QString getLayoutDescription() const;
 
-	~Result();
-
 private:
 	void createPhoto();
 	void createDocument();
+	void createGame();
 
 	enum class Type {
 		Unknown,
@@ -92,6 +91,7 @@ private:
 		Contact,
 		Geo,
 		Venue,
+		Game,
 	};
 
 	friend class internal::SendData;
@@ -112,6 +112,7 @@ private:
 
 	DocumentData *_document = nullptr;
 	PhotoData *_photo = nullptr;
+	GameData *_game = nullptr;
 
 	std_::unique_ptr<MTPReplyMarkup> _mtpKeyboard;
 
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
index d2f5354ec..c086bec36 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
@@ -90,5 +90,11 @@ UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const {
 	history->addNewDocument(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, _document, _caption, markup);
 }
 
+void SendGame::addToHistory(const Result *owner, History *history,
+	MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate,
+	UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const {
+	history->addNewGame(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, _game, markup);
+}
+
 } // namespace internal
 } // namespace InlineBots
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h
index 3c2e8fbf4..4b0c76ce4 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h
@@ -221,5 +221,25 @@ private:
 
 };
 
+// Message with game.
+class SendGame : public SendData {
+public:
+	SendGame(GameData *game)
+		: _game(game) {
+	}
+
+	bool isValid() const override {
+		return _game != nullptr;
+	}
+
+	void addToHistory(const Result *owner, History *history,
+		MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate,
+		UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const override;
+
+private:
+	GameData *_game;
+
+};
+
 } // namespace internal
 } // namespace InlineBots
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index cb6b37fe9..37ac2441b 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -3349,7 +3349,7 @@ void MainWidget::openLocalUrl(const QString &url) {
 	} else if (auto usernameMatch = regex_match(qsl("^resolve/?\\?(.+)(#|$)"), command, matchOptions)) {
 		auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower);
 		auto domain = params.value(qsl("domain"));
-		if (auto domainMatch = regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
+		if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
 			auto start = qsl("start");
 			auto startToken = params.value(start);
 			if (startToken.isEmpty()) {
@@ -3364,6 +3364,11 @@ void MainWidget::openLocalUrl(const QString &url) {
 			if (auto postId = postParam.toInt()) {
 				post = postId;
 			}
+			auto gameParam = params.value(qsl("game"));
+			if (!gameParam.isEmpty() && regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), gameParam, matchOptions)) {
+				startToken = gameParam;
+				post = ShowAtGameShareMsgId;
+			}
 			openPeerByName(domain, post, startToken);
 		}
 	} else if (auto shareGameScoreMatch = regex_match(qsl("^share_game_score/?\\?(.+)(#|$)"), command, matchOptions)) {
@@ -3377,7 +3382,14 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr
 
 	PeerData *peer = App::peerByName(username);
 	if (peer) {
-		if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
+		if (msgId == ShowAtGameShareMsgId) {
+			if (peer->isUser() && peer->asUser()->botInfo && !startToken.isEmpty()) {
+				peer->asUser()->botInfo->shareGameShortName = startToken;
+				Ui::showLayer(new ContactsBox(peer->asUser()));
+			} else {
+				Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
+			}
+		} else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
 			if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) {
 				peer->asUser()->botInfo->startGroupToken = startToken;
 				Ui::showLayer(new ContactsBox(peer->asUser()));
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 12545ed15..babee7e99 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -163,6 +163,7 @@ constexpr const MsgId ShowAtTheEndMsgId = -0x40000000;
 constexpr const MsgId SwitchAtTopMsgId = -0x3FFFFFFF;
 constexpr const MsgId ShowAtProfileMsgId = -0x3FFFFFFE;
 constexpr const MsgId ShowAndStartBotMsgId = -0x3FFFFFD;
+constexpr const MsgId ShowAtGameShareMsgId = -0x3FFFFFC;
 constexpr const MsgId ServerMaxMsgId = 0x3FFFFFFF;
 constexpr const MsgId ShowAtUnreadMsgId = 0;
 
@@ -385,7 +386,7 @@ struct BotInfo {
 	QList<BotCommand> commands;
 	Text text = Text{ int(st::msgMinWidth) }; // description
 
-	QString startToken, startGroupToken;
+	QString startToken, startGroupToken, shareGameShortName;
 	PeerId inlineReturnPeerId = 0;
 };
 
@@ -1141,7 +1142,7 @@ public:
 		return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
 	}
 	bool isMusic() const {
-		return (type == SongDocument) ? !static_cast<SongData*>(_additional.get())->title.isEmpty() : false;
+		return (type == SongDocument) ? (static_cast<SongData*>(_additional.get())->duration != 0) : false;
 	}
 	bool isVideo() const {
 		return (type == VideoDocument);