From cb2df51af6c7bc308330b615debe972f054d5eb9 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 1 Jan 2016 22:48:32 +0800
Subject: [PATCH] remembering last used inline bots, showing them in mentions
 dropdown, 9015006 beta

---
 Telegram/SourceFiles/application.cpp      |   7 +-
 Telegram/SourceFiles/config.h             |   3 +-
 Telegram/SourceFiles/dialogswidget.cpp    |  12 +-
 Telegram/SourceFiles/dropdown.cpp         |  96 ++++++--
 Telegram/SourceFiles/dropdown.h           |   8 +-
 Telegram/SourceFiles/facades.cpp          |   2 +-
 Telegram/SourceFiles/gui/flattextarea.cpp |  29 ++-
 Telegram/SourceFiles/gui/flattextarea.h   |   2 +-
 Telegram/SourceFiles/historywidget.cpp    |  35 ++-
 Telegram/SourceFiles/localstorage.cpp     | 288 ++++++++++++----------
 Telegram/SourceFiles/localstorage.h       |   4 +-
 Telegram/SourceFiles/mainwidget.cpp       |   4 +-
 Telegram/SourceFiles/settings.cpp         |   2 +
 Telegram/SourceFiles/settings.h           |   6 +-
 Telegram/Telegram.rc                      |   8 +-
 15 files changed, 308 insertions(+), 198 deletions(-)

diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index 421e2b443..9313e0b85 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -705,10 +705,11 @@ void Application::checkMapVersion() {
     if (Local::oldMapVersion() < AppVersion) {
 		if (Local::oldMapVersion()) {
 			QString versionFeatures;
-			if (cDevVersion() && Local::oldMapVersion() < 9014) {
-				versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D));
+			if (cDevVersion() && Local::oldMapVersion() < 9016) {
+//				versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D));
+				versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed();
 			} else if (Local::oldMapVersion() < 9015) {
-//				versionFeatures = lang(lng_new_version_text).trimmed();
+				versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed();
 			} else {
 				versionFeatures = lang(lng_new_version_minor).trimmed();
 			}
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 4926981de..2875cabd1 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
 static const int32 AppVersion = 9015;
 static const wchar_t *AppVersionStr = L"0.9.15";
 static const bool DevVersion = false;
-#define BETA_VERSION (9015005ULL) // just comment this line to build public version
+#define BETA_VERSION (9015006ULL) // just comment this line to build public version
 
 static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
 static const wchar_t *AppName = L"Telegram Desktop";
@@ -87,6 +87,7 @@ enum {
 	AverageGifSize = 320 * 240,
 	WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it
 	InlineBotRequestDelay = 400, // wait 400ms before context bot realtime request
+	RecentInlineBotsLimit = 10,
 
 	AVBlockSize = 4096, // 4Kb for ffmpeg blocksize
 
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index d46b65a4f..ef5247c88 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -163,7 +163,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 				PeerData *act = App::main()->activePeer();
 				MsgId actId = App::main()->activeMsgId();
 				for (; from < to; ++from) {
-					bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId;					
+					bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId;
 					bool selected = (from == _filteredSel) || (_filterResults[from]->history->peer == _menuPeer);
 					_filterResults[from]->paint(p, w, active, selected, paintingOther);
 					p.translate(0, st::dlgHeight);
@@ -872,7 +872,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) {
 	}
 	_hashtagFilter = newFilter.toString();
 	if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) {
-		Local::readRecentHashtags();
+		Local::readRecentHashtagsAndBots();
 	}
 	const RecentHashtagPack &recent(cRecentSearchHashtags());
 	_hashtagResults.clear();
@@ -1385,7 +1385,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
 		if (from < _filterResults.size()) {
 			int32 to = (yTo / int32(st::dlgHeight)) + 1, w = width();
 			if (to > _filterResults.size()) to = _filterResults.size();
-			
+
 			for (; from < to; ++from) {
 				_filterResults[from]->history->peer->photo->load();
 			}
@@ -1434,7 +1434,7 @@ bool DialogsInner::choosePeer() {
 					}
 				}
 				cSetRecentSearchHashtags(recent);
-				Local::writeRecentHashtags();
+				Local::writeRecentHashtagsAndBots();
 				emit refreshHashtags();
 
 				selByMouse = true;
@@ -1487,7 +1487,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
 			}
 		}
 		if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) {
-			Local::readRecentHashtags();
+			Local::readRecentHashtagsAndBots();
 			recent = cRecentSearchHashtags();
 		}
 		found = true;
@@ -1495,7 +1495,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
 	}
 	if (found) {
 		cSetRecentSearchHashtags(recent);
-		Local::writeRecentHashtags();
+		Local::writeRecentHashtagsAndBots();
 	}
 }
 
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index d48301709..85dfa1047 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -3766,7 +3766,7 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
 		if (selected) {
 			p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b);
 			int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
-			if (!_hrows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon);
+			if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon);
 		}
 		p.setPen(st::black->p);
 		if (!_mrows->isEmpty()) {
@@ -3902,6 +3902,10 @@ bool MentionsInner::select() {
 	return false;
 }
 
+void MentionsInner::setRecentInlineBotsInRows(int32 bots) {
+	_recentInlineBotsInRows = bots;
+}
+
 QString MentionsInner::getSelected() const {
 	int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size());
 	if (_sel >= 0 && _sel < maxSel) {
@@ -3930,20 +3934,32 @@ void MentionsInner::mousePressEvent(QMouseEvent *e) {
 	_mouseSel = true;
 	onUpdateSelected(true);
 	if (e->button() == Qt::LeftButton) {
-		if (_overDelete && _sel >= 0 && _sel < _hrows->size()) {
+		if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
 			_mousePos = mapToGlobal(e->pos());
-
-			QString toRemove = _hrows->at(_sel);
-			RecentHashtagPack recent(cRecentWriteHashtags());
-			for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
-				if (i->first == toRemove) {
-					i = recent.erase(i);
-				} else {
-					++i;
+			bool removed = false;
+			if (_mrows->isEmpty()) {
+				QString toRemove = _hrows->at(_sel);
+				RecentHashtagPack &recent(cRefRecentWriteHashtags());
+				for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
+					if (i->first == toRemove) {
+						i = recent.erase(i);
+						removed = true;
+					} else {
+						++i;
+					}
+				}
+			} else {
+				UserData *toRemove = _mrows->at(_sel);
+				RecentInlineBots &recent(cRefRecentInlineBots());
+				int32 index = recent.indexOf(toRemove);
+				if (index >= 0) {
+					recent.remove(index);
+					removed = true;
 				}
 			}
-			cSetRecentWriteHashtags(recent);
-			Local::writeRecentHashtags();
+			if (removed) {
+				Local::writeRecentHashtagsAndBots();
+			}
 			_parent->updateFiltered();
 
 			_mouseSel = true;
@@ -3980,8 +3996,8 @@ void MentionsInner::onUpdateSelected(bool force) {
 	if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
 
 	int w = width(), mouseY = mouse.y();
-	_overDelete = _mrows->isEmpty() && (mouse.x() >= w - st::mentionHeight);
 	int32 sel = mouseY / int32(st::mentionHeight), maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
+	_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= w - st::mentionHeight) : false;
 	if (sel < 0 || sel >= maxSel) {
 		sel = -1;
 	}
@@ -4043,15 +4059,23 @@ void MentionsDropdown::paintEvent(QPaintEvent *e) {
 
 }
 
-void MentionsDropdown::showFiltered(PeerData *peer, QString start) {
+void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) {
+	if (query.isEmpty() || (peer->isUser() && query.at(0) == '@' && (!start || cRecentInlineBots().isEmpty()))) {
+		if (!isHidden()) {
+			hideStart();
+		}
+		return;
+	}
+
 	_chat = peer->asChat();
 	_user = peer->asUser();
 	_channel = peer->asChannel();
-	start = start.toLower();
-	bool toDown = (_filter != start);
+	query = query.toLower();
+	bool toDown = (_filter != query);
 	if (toDown) {
-		_filter = start;
+		_filter = query;
 	}
+	_addInlineBots = start;
 
 	updateFiltered(toDown);
 }
@@ -4063,13 +4087,34 @@ bool MentionsDropdown::clearFilteredBotCommands() {
 }
 
 void MentionsDropdown::updateFiltered(bool toDown) {
-	int32 now = unixtime();
-	MentionRows rows;
+	int32 now = unixtime(), recentInlineBots = 0;
+	MentionRows mrows;
 	HashtagRows hrows;
 	BotCommandRows brows;
+	if (_filter.at(0) == '@') {
+		if (_chat) {
+			mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
+		} else if (_channel && _channel->isMegagroup()) {
+			if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
+			} else {
+				mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size());
+			}
+		} else if (_addInlineBots) {
+			mrows.reserve(cRecentInlineBots().size());
+		}
+		if (_addInlineBots) {
+			for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) {
+				UserData *user = *i;
+				if (user->username.isEmpty()) continue;
+				if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
+				mrows.push_back(user);
+				++recentInlineBots;
+			}
+		}
+	}
 	if (_filter.at(0) == '@' && _chat) {
 		QMultiMap<int32, UserData*> ordered;
-		rows.reserve(_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size());
+		mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
 		if (_chat->noParticipantInfo()) {
 			if (App::api()) App::api()->requestFullPeer(_chat);
 		} else if (!_chat->participants.isEmpty()) {
@@ -4084,7 +4129,7 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 			UserData *user = *i;
 			if (user->username.isEmpty()) continue;
 			if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
-			rows.push_back(user);
+			mrows.push_back(user);
 			if (!ordered.isEmpty()) {
 				ordered.remove(App::onlineForSort(user, now), user);
 			}
@@ -4092,7 +4137,7 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 		if (!ordered.isEmpty()) {
 			for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
 				--i;
-				rows.push_back(i.value());
+				mrows.push_back(i.value());
 			}
 		}
 	} else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) {
@@ -4100,12 +4145,12 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 		if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
 			if (App::api()) App::api()->requestLastParticipants(_channel);
 		} else {
-			rows.reserve(_channel->mgInfo->lastParticipants.size());
+			mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
 			for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
 				UserData *user = *i;
 				if (user->username.isEmpty()) continue;
 				if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
-				rows.push_back(user);
+				mrows.push_back(user);
 			}
 		}
 	} else if (_filter.at(0) == '#') {
@@ -4184,7 +4229,8 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 			}
 		}
 	}
-	rowsUpdated(rows, hrows, brows, toDown);
+	rowsUpdated(mrows, hrows, brows, toDown);
+	_inner.setRecentInlineBotsInRows(recentInlineBots);
 }
 
 void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown) {
diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h
index b86a1d6f2..75ce7c634 100644
--- a/Telegram/SourceFiles/dropdown.h
+++ b/Telegram/SourceFiles/dropdown.h
@@ -736,6 +736,8 @@ public:
 	bool moveSel(int direction);
 	bool select();
 
+	void setRecentInlineBotsInRows(int32 bots);
+
 	QString getSelected() const;
 
 signals:
@@ -756,6 +758,7 @@ private:
 	MentionRows *_mrows;
 	HashtagRows *_hrows;
 	BotCommandRows *_brows;
+	int32 _recentInlineBotsInRows;
 	int32 _sel;
 	bool _mouseSel;
 	QPoint _mousePos;
@@ -775,7 +778,7 @@ public:
 	void fastHide();
 
 	bool clearFilteredBotCommands();
-	void showFiltered(PeerData *peer, QString start);
+	void showFiltered(PeerData *peer, QString query, bool start);
 	void updateFiltered(bool toDown = false);
 	void setBoundings(QRect boundings);
 
@@ -820,7 +823,7 @@ private:
 	HashtagRows _hrows;
 	BotCommandRows _brows;
 
-	void rowsUpdated(const MentionRows &rows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown);
+	void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown);
 
 	ScrollArea _scroll;
 	MentionsInner _inner;
@@ -830,6 +833,7 @@ private:
 	ChannelData *_channel;
 	QString _filter;
 	QRect _boundings;
+	bool _addInlineBots;
 
 	int32 _width, _height;
 	bool _hiding;
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index 2c5db2dfd..09b132740 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -129,7 +129,7 @@ namespace Ui {
 
 	void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) {
 		if (MainWidget *m = App::main()) {
-			QMetaObject::invokeMethod(m, SLOT(ui_showPeerHistoryAsync(quint64,qint32)), Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId));
+			QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId));
 		}
 	}
 
diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp
index ae09ce169..4b88c0110 100644
--- a/Telegram/SourceFiles/gui/flattextarea.cpp
+++ b/Telegram/SourceFiles/gui/flattextarea.cpp
@@ -270,7 +270,9 @@ EmojiPtr FlatTextarea::getSingleEmoji() const {
 	return 0;
 }
 
-void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&inlineBot, QString &inlineBotUsername) const {
+QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start, UserData *&inlineBot, QString &inlineBotUsername) const {
+	start = false;
+
 	// check inline bot query
 	const QString &text(getLastText());
 	int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size();
@@ -301,13 +303,12 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i
 					inlineBot = InlineBotLookingUpData;
 				}
 			}
-			if (inlineBot == InlineBotLookingUpData) return;
+			if (inlineBot == InlineBotLookingUpData) return QString();
 
 			if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) {
 				inlineBot = 0;
 			} else {
-				start = text.mid(inlineUsernameStart + inlineUsernameLength + 1);
-				return;
+				return text.mid(inlineUsernameStart + inlineUsernameLength + 1);
 			}
 		} else {
 			inlineUsernameLength = 0;
@@ -319,7 +320,7 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i
 	}
 
 	int32 pos = textCursor().position();
-	if (textCursor().anchor() != pos) return;
+	if (textCursor().anchor() != pos) return QString();
 
 	// check mention / hashtag / bot command
 	QTextDocument *doc(document());
@@ -339,29 +340,33 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i
 		for (int i = pos - p; i > 0; --i) {
 			if (t.at(i - 1) == '@') {
 				if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
-					start = t.mid(i - 1, pos - p - i + 1);
+					start = (i == 1) && (p == 0);
+					return t.mid(i - 1, pos - p - i + 1);
 				} else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
 					mentionInCommand = true;
 					--i;
 					continue;
 				}
-				return;
+				return QString();
 			} else if (t.at(i - 1) == '#') {
 				if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) {
-					start = t.mid(i - 1, pos - p - i + 1);
+					start = (i == 1) && (p == 0);
+					return t.mid(i - 1, pos - p - i + 1);
 				}
-				return;
+				return QString();
 			} else if (t.at(i - 1) == '/') {
 				if (i < 2) {
-					start = t.mid(i - 1, pos - p - i + 1);
+					start = (i == 1) && (p == 0);
+					return t.mid(i - 1, pos - p - i + 1);
 				}
-				return;
+				return QString();
 			}
 			if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
 			if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
 		}
-		return;
+		break;
 	}
+	return QString();
 }
 
 void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h
index 8020a8d95..c7e74efde 100644
--- a/Telegram/SourceFiles/gui/flattextarea.h
+++ b/Telegram/SourceFiles/gui/flattextarea.h
@@ -64,7 +64,7 @@ public:
 	QSize minimumSizeHint() const;
 
 	EmojiPtr getSingleEmoji() const;
-	void getMentionHashtagBotCommandStart(QString &start, UserData *&contextBot, QString &contextBotUsername) const;
+	QString getMentionHashtagBotCommandPart(bool &start, UserData *&contextBot, QString &contextBotUsername) const;
 	void removeSingleEmoji();
 	bool hasText() const;
 
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index e11484216..a373a9413 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -5338,8 +5338,9 @@ void HistoryWidget::onCheckMentionDropdown() {
 	if (!_history || _a_show.animating()) return;
 
 	UserData *bot = _inlineBot;
-	QString start, inlineBotUsername(_inlineBotUsername);
-	_field.getMentionHashtagBotCommandStart(start, _inlineBot, _inlineBotUsername);
+	bool start = false;
+	QString inlineBotUsername(_inlineBotUsername);
+	QString query = _field.getMentionHashtagBotCommandPart(start, _inlineBot, _inlineBotUsername);
 	if (inlineBotUsername != _inlineBotUsername) {
 		if (_inlineBotResolveRequestId) {
 			Notify::inlineBotRequesting(false);
@@ -5359,10 +5360,10 @@ void HistoryWidget::onCheckMentionDropdown() {
 		if (_inlineBot != bot) {
 			updateFieldPlaceholder();
 		}
-		if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && start.isEmpty()) {
+		if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && query.isEmpty()) {
 			_emojiPan.clearInlineBot();
 		} else {
-			_emojiPan.queryInlineBot(_inlineBot, start);
+			_emojiPan.queryInlineBot(_inlineBot, query);
 		}
 		if (!_attachMention.isHidden()) {
 			_attachMention.hideStart();
@@ -5373,16 +5374,12 @@ void HistoryWidget::onCheckMentionDropdown() {
 			_field.finishPlaceholder();
 		}
 		_emojiPan.clearInlineBot();
-		if (!start.isEmpty()) {
-			if (start.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtags();
-			if (start.at(0) == '@' && _peer->isUser()) return;
-			if (start.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
-			_attachMention.showFiltered(_peer, start);
-		} else {
-			if (!_attachMention.isHidden()) {
-				_attachMention.hideStart();
-			}
+		if (!query.isEmpty()) {
+			if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots();
+			if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
+			if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
 		}
+		_attachMention.showFiltered(_peer, query, start);
 	}
 }
 
@@ -6428,6 +6425,18 @@ void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) {
 	_saveDraftStart = getms();
 	onDraftSave();
 
+	RecentInlineBots &bots(cRefRecentInlineBots());
+	int32 index = bots.indexOf(bot);
+	if (index) {
+		if (index > 0) {
+			bots.removeAt(index);
+		} else if (bots.size() >= RecentInlineBotsLimit) {
+			bots.resize(RecentInlineBotsLimit - 1);
+		}
+		bots.push_front(bot);
+		Local::writeRecentHashtagsAndBots();
+	}
+
 	onCheckMentionDropdown();
 	if (!_attachType.isHidden()) _attachType.hideStart();
 
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index d7dfc9d9e..d6deee939 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -538,22 +538,22 @@ namespace {
 	FileKey _dataNameKey = 0;
 
 	enum { // Local Storage Keys
-		lskUserMap            = 0x00,
-		lskDraft              = 0x01, // data: PeerId peer
-		lskDraftPosition      = 0x02, // data: PeerId peer
-		lskImages             = 0x03, // data: StorageKey location
-		lskLocations          = 0x04, // no data
-		lskStickerImages      = 0x05, // data: StorageKey location
-		lskAudios             = 0x06, // data: StorageKey location
-		lskRecentStickersOld  = 0x07, // no data
-		lskBackground         = 0x08, // no data
-		lskUserSettings       = 0x09, // no data
-		lskRecentHashtags     = 0x0a, // no data
-		lskStickers           = 0x0b, // no data
-		lskSavedPeers         = 0x0c, // no data
-		lskReportSpamStatuses = 0x0d, // no data
-		lskSavedGifsOld       = 0x0e,
-		lskSavedGifs          = 0x0f,
+		lskUserMap               = 0x00,
+		lskDraft                 = 0x01, // data: PeerId peer
+		lskDraftPosition         = 0x02, // data: PeerId peer
+		lskImages                = 0x03, // data: StorageKey location
+		lskLocations             = 0x04, // no data
+		lskStickerImages         = 0x05, // data: StorageKey location
+		lskAudios                = 0x06, // data: StorageKey location
+		lskRecentStickersOld     = 0x07, // no data
+		lskBackground            = 0x08, // no data
+		lskUserSettings          = 0x09, // no data
+		lskRecentHashtagsAndBots = 0x0a, // no data
+		lskStickers              = 0x0b, // no data
+		lskSavedPeers            = 0x0c, // no data
+		lskReportSpamStatuses    = 0x0d, // no data
+		lskSavedGifsOld          = 0x0e, // no data
+		lskSavedGifs             = 0x0f, // no data
 	};
 
 	typedef QMap<PeerId, FileKey> DraftsMap;
@@ -581,8 +581,8 @@ namespace {
 	bool _backgroundWasRead = false;
 
 	FileKey _userSettingsKey = 0;
-	FileKey _recentHashtagsKey = 0;
-	bool _recentHashtagsWereRead = false;
+	FileKey _recentHashtagsAndBotsKey = 0;
+	bool _recentHashtagsAndBotsWereRead = false;
 
 	FileKey _savedPeersKey = 0;
 
@@ -726,18 +726,20 @@ namespace {
 				_fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond));
 			}
 
-			_storageWebFilesSize = 0;
-			_webFilesMap.clear();
+			if (!locations.stream.atEnd()) {
+				_storageWebFilesSize = 0;
+				_webFilesMap.clear();
 
-			quint32 webLocationsCount;
-			locations.stream >> webLocationsCount;
-			for (quint32 i = 0; i < webLocationsCount; ++i) {
-				QString url;
-				quint64 key;
-				qint32 size;
-				locations.stream >> url >> key >> size;
-				_webFilesMap.insert(url, FileDesc(key, size));
-				_storageWebFilesSize += size;
+				quint32 webLocationsCount;
+				locations.stream >> webLocationsCount;
+				for (quint32 i = 0; i < webLocationsCount; ++i) {
+					QString url;
+					quint64 key;
+					qint32 size;
+					locations.stream >> url >> key >> size;
+					_webFilesMap.insert(url, FileDesc(key, size));
+					_storageWebFilesSize += size;
+				}
 			}
 		}
 	}
@@ -1667,7 +1669,7 @@ namespace {
 		qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0;
 		quint64 locationsKey = 0, reportSpamStatusesKey = 0;
 		quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0;
-		quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0;
+		quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0;
 		while (!map.stream.atEnd()) {
 			quint32 keyType;
 			map.stream >> keyType;
@@ -1744,8 +1746,8 @@ namespace {
 			case lskUserSettings: {
 				map.stream >> userSettingsKey;
 			} break;
-			case lskRecentHashtags: {
-				map.stream >> recentHashtagsKey;
+			case lskRecentHashtagsAndBots: {
+				map.stream >> recentHashtagsAndBotsKey;
 			} break;
 			case lskStickers: {
 				map.stream >> stickersKey;
@@ -1788,7 +1790,7 @@ namespace {
 		_savedPeersKey = savedPeersKey;
 		_backgroundKey = backgroundKey;
 		_userSettingsKey = userSettingsKey;
-		_recentHashtagsKey = recentHashtagsKey;
+		_recentHashtagsAndBotsKey = recentHashtagsAndBotsKey;
 		_oldMapVersion = mapData.version;
 		if (_oldMapVersion < AppVersion) {
 			_mapChanged = true;
@@ -1861,7 +1863,7 @@ namespace {
 		if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
-		if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64);
+		if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 		EncryptedDescriptor mapData(mapSize);
 		if (!_draftsMap.isEmpty()) {
 			mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
@@ -1917,8 +1919,8 @@ namespace {
 		if (_userSettingsKey) {
 			mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey);
 		}
-		if (_recentHashtagsKey) {
-			mapData.stream << quint32(lskRecentHashtags) << quint64(_recentHashtagsKey);
+		if (_recentHashtagsAndBotsKey) {
+			mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey);
 		}
 		map.writeEncrypted(mapData);
 
@@ -2187,7 +2189,7 @@ namespace Local {
 		_storageWebFilesSize = 0;
 		_locationsKey = _reportSpamStatusesKey = 0;
 		_recentStickersKeyOld = _stickersKey = _savedGifsKey = 0;
-		_backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0;
+		_backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0;
 		_oldMapVersion = _oldSettingsVersion = 0;
 		_mapChanged = true;
 		_writeMap(WriteMapNow);
@@ -3258,89 +3260,6 @@ namespace Local {
 		return false;
 	}
 
-	void writeRecentHashtags() {
-		if (!_working()) return;
-
-		const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags());
-		if (write.isEmpty() && search.isEmpty()) readRecentHashtags();
-		if (write.isEmpty() && search.isEmpty()) {
-			if (_recentHashtagsKey) {
-				clearKey(_recentHashtagsKey);
-				_recentHashtagsKey = 0;
-				_mapChanged = true;
-			}
-			_writeMap();
-		} else {
-			if (!_recentHashtagsKey) {
-				_recentHashtagsKey = genKey();
-				_mapChanged = true;
-				_writeMap(WriteMapFast);
-			}
-			quint32 size = sizeof(quint32) * 2, writeCnt = 0, searchCnt = 0;
-			for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) {
-				if (!i->first.isEmpty()) {
-					size += _stringSize(i->first) + sizeof(quint16);
-					++writeCnt;
-				}
-			}
-			for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) {
-				if (!i->first.isEmpty()) {
-					size += _stringSize(i->first) + sizeof(quint16);
-					++searchCnt;
-				}
-			}
-			EncryptedDescriptor data(size);
-			data.stream << quint32(writeCnt) << quint32(searchCnt);
-			for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) {
-				if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
-			}
-			for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) {
-				if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
-			}
-			FileWriteDescriptor file(_recentHashtagsKey);
-			file.writeEncrypted(data);
-		}
-	}
-
-	void readRecentHashtags() {
-		if (_recentHashtagsWereRead) return;
-		_recentHashtagsWereRead = true;
-
-		if (!_recentHashtagsKey) return;
-
-		FileReadDescriptor hashtags;
-		if (!readEncryptedFile(hashtags, _recentHashtagsKey)) {
-			clearKey(_recentHashtagsKey);
-			_recentHashtagsKey = 0;
-			_writeMap();
-			return;
-		}
-
-		quint32 writeCount = 0, searchCount = 0;
-		hashtags.stream >> writeCount >> searchCount;
-
-		QString tag;
-		quint16 count;
-
-		RecentHashtagPack write, search;
-		if (writeCount) {
-			write.reserve(writeCount);
-			for (uint32 i = 0; i < writeCount; ++i) {
-				hashtags.stream >> tag >> count;
-				write.push_back(qMakePair(tag.trimmed(), count));
-			}
-		}
-		if (searchCount) {
-			search.reserve(searchCount);
-			for (uint32 i = 0; i < searchCount; ++i) {
-				hashtags.stream >> tag >> count;
-				search.push_back(qMakePair(tag.trimmed(), count));
-			}
-		}
-		cSetRecentWriteHashtags(write);
-		cSetRecentSearchHashtags(search);
-	}
-
 	uint32 _peerSize(PeerData *peer) {
 		uint32 result = sizeof(quint64) + sizeof(quint64) + _storageImageLocationSize();
 		if (peer->isUser()) {
@@ -3370,7 +3289,7 @@ namespace Local {
 		return result;
 	}
 
-	void _writePeer(QDataStream &stream, PeerData *peer) {
+	void _writePeer(QDataStream &stream, PeerData *peer, int32 fileVersion = AppVersion) {
 		stream << quint64(peer->id) << quint64(peer->photoId);
 		_writeStorageImageLocation(stream, peer->photoLoc);
 		if (peer->isUser()) {
@@ -3380,6 +3299,9 @@ namespace Local {
 			if (AppVersion >= 9012) {
 				stream << qint32(user->flags);
 			}
+			if (AppVersion >= 9016 || fileVersion >= 9016) {
+				stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString());
+			}
 			stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1);
 		} else if (peer->isChat()) {
 			ChatData *chat = peer->asChat();
@@ -3396,25 +3318,31 @@ namespace Local {
 		}
 	}
 
-	PeerData *_readPeer(FileReadDescriptor &from) {
+	PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) {
 		PeerData *result = 0;
 		quint64 peerId = 0, photoId = 0;
 		from.stream >> peerId >> photoId;
 
 		StorageImageLocation photoLoc(_readStorageImageLocation(from));
 
+		result = App::peerLoaded(peerId);
+		if (result && result->loaded) return result;
+
 		result = App::peer(peerId);
 		result->loaded = true;
 		if (result->isUser()) {
 			UserData *user = result->asUser();
 
-			QString first, last, phone, username;
+			QString first, last, phone, username, inlinePlaceholder;
 			quint64 access;
 			qint32 flags = 0, onlineTill, contact, botInfoVersion;
 			from.stream >> first >> last >> phone >> username >> access;
 			if (from.version >= 9012) {
 				from.stream >> flags;
 			}
+			if (from.version >= 9016 || fileVersion >= 9016) {
+				from.stream >> inlinePlaceholder;
+			}
 			from.stream >> onlineTill >> contact >> botInfoVersion;
 
 			bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0);
@@ -3427,6 +3355,9 @@ namespace Local {
 			user->onlineTill = onlineTill;
 			user->contact = contact;
 			user->setBotInfoVersion(botInfoVersion);
+			if (!inlinePlaceholder.isEmpty() && user->botInfo) {
+				user->botInfo->inlinePlaceholder = inlinePlaceholder;
+			}
 
 			if (peerToUser(user->id) == MTP::authedId()) {
 				user->input = MTP_inputPeerSelf();
@@ -3489,6 +3420,113 @@ namespace Local {
 		return result;
 	}
 
+	void writeRecentHashtagsAndBots() {
+		if (!_working()) return;
+
+		const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags());
+		const RecentInlineBots &bots(cRecentInlineBots());
+		if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots();
+		if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) {
+			if (_recentHashtagsAndBotsKey) {
+				clearKey(_recentHashtagsAndBotsKey);
+				_recentHashtagsAndBotsKey = 0;
+				_mapChanged = true;
+			}
+			_writeMap();
+		} else {
+			if (!_recentHashtagsAndBotsKey) {
+				_recentHashtagsAndBotsKey = genKey();
+				_mapChanged = true;
+				_writeMap(WriteMapFast);
+			}
+			quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size();
+			for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e;  ++i) {
+				if (!i->first.isEmpty()) {
+					size += _stringSize(i->first) + sizeof(quint16);
+					++writeCnt;
+				}
+			}
+			for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) {
+				if (!i->first.isEmpty()) {
+					size += _stringSize(i->first) + sizeof(quint16);
+					++searchCnt;
+				}
+			}
+			for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
+				size += _peerSize(*i);
+			}
+
+			EncryptedDescriptor data(size);
+			data.stream << quint32(writeCnt) << quint32(searchCnt);
+			for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) {
+				if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
+			}
+			for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) {
+				if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
+			}
+			data.stream << quint32(botsCnt);
+			for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
+				_writePeer(data.stream, *i, 9016);
+			}
+			FileWriteDescriptor file(_recentHashtagsAndBotsKey);
+			file.writeEncrypted(data);
+		}
+	}
+
+	void readRecentHashtagsAndBots() {
+		if (_recentHashtagsAndBotsWereRead) return;
+		_recentHashtagsAndBotsWereRead = true;
+
+		if (!_recentHashtagsAndBotsKey) return;
+
+		FileReadDescriptor hashtags;
+		if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) {
+			clearKey(_recentHashtagsAndBotsKey);
+			_recentHashtagsAndBotsKey = 0;
+			_writeMap();
+			return;
+		}
+
+		quint32 writeCount = 0, searchCount = 0, botsCount = 0;
+		hashtags.stream >> writeCount >> searchCount;
+
+		QString tag;
+		quint16 count;
+
+		RecentHashtagPack write, search;
+		RecentInlineBots bots;
+		if (writeCount) {
+			write.reserve(writeCount);
+			for (uint32 i = 0; i < writeCount; ++i) {
+				hashtags.stream >> tag >> count;
+				write.push_back(qMakePair(tag.trimmed(), count));
+			}
+		}
+		if (searchCount) {
+			search.reserve(searchCount);
+			for (uint32 i = 0; i < searchCount; ++i) {
+				hashtags.stream >> tag >> count;
+				search.push_back(qMakePair(tag.trimmed(), count));
+			}
+		}
+		cSetRecentWriteHashtags(write);
+		cSetRecentSearchHashtags(search);
+
+		if (!hashtags.stream.atEnd()) {
+			hashtags.stream >> botsCount;
+			if (botsCount) {
+				bots.reserve(botsCount);
+				for (uint32 i = 0; i < botsCount; ++i) {
+					PeerData *peer = _readPeer(hashtags, 9016);
+					if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) {
+						bots.push_back(peer->asUser());
+					}
+				}
+			}
+			cSetRecentInlineBots(bots);
+		}
+	}
+
 	void writeSavedPeers() {
 		if (!_working()) return;
 
@@ -3651,8 +3689,8 @@ namespace Local {
 				_stickersKey = 0;
 				_mapChanged = true;
 			}
-			if (_recentHashtagsKey) {
-				_recentHashtagsKey = 0;
+			if (_recentHashtagsAndBotsKey) {
+				_recentHashtagsAndBotsKey = 0;
 				_mapChanged = true;
 			}
 			if (_savedPeersKey) {
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
index aa4f51432..6bf8e7c9d 100644
--- a/Telegram/SourceFiles/localstorage.h
+++ b/Telegram/SourceFiles/localstorage.h
@@ -157,8 +157,8 @@ namespace Local {
 	void writeBackground(int32 id, const QImage &img);
 	bool readBackground();
 
-	void writeRecentHashtags();
-	void readRecentHashtags();
+	void writeRecentHashtagsAndBots();
+	void readRecentHashtagsAndBots();
 
 	void addSavedPeer(PeerData *peer, const QDateTime &position);
 	void removeSavedPeer(PeerData *peer);
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index ba51e53d2..e73ed08df 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -1349,7 +1349,7 @@ void MainWidget::saveRecentHashtags(const QString &text) {
 			}
 		}
 		if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) {
-			Local::readRecentHashtags();
+			Local::readRecentHashtagsAndBots();
 			recent = cRecentWriteHashtags();
 		}
 		found = true;
@@ -1357,7 +1357,7 @@ void MainWidget::saveRecentHashtags(const QString &text) {
 	}
 	if (found) {
 		cSetRecentWriteHashtags(recent);
-		Local::writeRecentHashtags();
+		Local::writeRecentHashtagsAndBots();
 	}
 }
 
diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp
index 6fa99541b..8b2700c4e 100644
--- a/Telegram/SourceFiles/settings.cpp
+++ b/Telegram/SourceFiles/settings.cpp
@@ -121,6 +121,8 @@ int32 gSavedGifsLimit = 100;
 
 RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags;
 
+RecentInlineBots gRecentInlineBots;
+
 bool gPasswordRecovered = false;
 int32 gPasscodeBadTries = 0;
 uint64 gPasscodeLastTry = 0;
diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h
index a5aac46f1..25b34f573 100644
--- a/Telegram/SourceFiles/settings.h
+++ b/Telegram/SourceFiles/settings.h
@@ -230,9 +230,13 @@ DeclareSetting(bool, ShowingSavedGifs);
 DeclareSetting(int32, SavedGifsLimit);
 
 typedef QList<QPair<QString, ushort> > RecentHashtagPack;
-DeclareSetting(RecentHashtagPack, RecentWriteHashtags);
+DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags);
 DeclareSetting(RecentHashtagPack, RecentSearchHashtags);
 
+class UserData;
+typedef QVector<UserData*> RecentInlineBots;
+DeclareRefSetting(RecentInlineBots, RecentInlineBots);
+
 DeclareSetting(bool, PasswordRecovered);
 
 DeclareSetting(int32, PasscodeBadTries);
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index af8a70d02..630f3982e 100644
--- a/Telegram/Telegram.rc
+++ b/Telegram/Telegram.rc
@@ -34,8 +34,8 @@ IDI_ICON1               ICON                    "SourceFiles\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,9,15,5
- PRODUCTVERSION 0,9,15,5
+ FILEVERSION 0,9,15,6
+ PRODUCTVERSION 0,9,15,6
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -51,10 +51,10 @@ BEGIN
         BLOCK "040904b0"
         BEGIN
             VALUE "CompanyName", "Telegram Messenger LLP"
-            VALUE "FileVersion", "0.9.15.5"
+            VALUE "FileVersion", "0.9.15.6"
             VALUE "LegalCopyright", "Copyright (C) 2013"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "0.9.15.5"
+            VALUE "ProductVersion", "0.9.15.6"
         END
     END
     BLOCK "VarFileInfo"