diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat
index 0ff3979ad..242dbf2d4 100644
--- a/Telegram/PrepareWin.bat
+++ b/Telegram/PrepareWin.bat
@@ -1,9 +1,9 @@
 @echo OFF
 
-set "AppVersion=8028"
-set "AppVersionStrSmall=0.8.28"
-set "AppVersionStr=0.8.28"
-set "AppVersionStrFull=0.8.28.0"
+set "AppVersion=8029"
+set "AppVersionStrSmall=0.8.29"
+set "AppVersionStr=0.8.29"
+set "AppVersionStrFull=0.8.29.0"
 set "DevChannel=1"
 
 if %DevChannel% neq 0 goto preparedev
diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt
index f4e0cf20d..4c0e021dd 100644
--- a/Telegram/Resources/style.txt
+++ b/Telegram/Resources/style.txt
@@ -1931,8 +1931,6 @@ mentionFg: #777;
 mentionFgOver: #707070;
 mentionFgActive: #0080c0;
 mentionFgOverActive: #0077b3;
-botCommandFont: font(fsize semibold);
-botDescFont: font(fsize italic);
 
 sessionsHeight: 440px;
 sessionHeight: 70px;
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 935ba401a..119b295fd 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -1999,7 +1999,7 @@ namespace App {
 		case mtpc_replyKeyboardMarkup: {
 			const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup());
 			data.flags = d.vflags.v;
-
+			
 			const QVector<MTPKeyboardButtonRow> &v(d.vrows.c_vector().v);
 			if (!v.isEmpty()) {
 				commands.reserve(v.size());
@@ -2028,6 +2028,18 @@ namespace App {
 				}
 			}
 		} break;
+
+		case mtpc_replyKeyboardHide: {
+			const MTPDreplyKeyboardHide &d(markup.c_replyKeyboardHide());
+			if (d.vflags.v) {
+				replyMarkups.insert(msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_ZERO));
+			}
+		} break;
+
+		case mtpc_replyKeyboardForceReply: {
+			const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply());
+			replyMarkups.insert(msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_FORCE_REPLY));
+		} break;
 		}
 	}
 
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index d419a839e..113830001 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -640,7 +640,7 @@ void Application::checkMapVersion() {
 		psRegisterCustomScheme();
 		if (Local::oldMapVersion()) {
 			QString versionFeatures;
-			if (DevChannel && Local::oldMapVersion() < 8028) {
+			if (DevChannel && Local::oldMapVersion() < 8029) {
 				versionFeatures = lang(lng_new_version_minor);// QString::fromUtf8("\xe2\x80\x94 IPv6 connections support\n\xe2\x80\x94 Bug fixes and minor stuff");// .replace('@', qsl("@") + QChar(0x200D));
 			} else if (!DevChannel && Local::oldMapVersion() < 8024) {
 				versionFeatures = lng_new_version_text(lt_blog_link, qsl("https://telegram.org/blog/bot-revolution"));// lang(lng_new_version_text).trimmed();
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 103dffff5..3205b6c56 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
-static const int32 AppVersion = 8028;
-static const wchar_t *AppVersionStr = L"0.8.28";
+static const int32 AppVersion = 8029;
+static const wchar_t *AppVersionStr = L"0.8.29";
 static const bool DevChannel = true;
 
 static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index f1ed16f50..edef4518a 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -2508,16 +2508,16 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
 
 			int32 addleft = 0, widthleft = htagwidth;
 			QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1);
-			int32 firstwidth = st::botCommandFont->m.width(first), secondwidth = st::botCommandFont->m.width(second);
+			int32 firstwidth = st::mentionFont->m.width(first), secondwidth = st::mentionFont->m.width(second);
 			if (htagwidth < firstwidth + secondwidth) {
-				if (htagwidth < firstwidth + st::botCommandFont->elidew) {
-					first = st::botCommandFont->m.elidedText(first + second, Qt::ElideRight, htagwidth);
+				if (htagwidth < firstwidth + st::mentionFont->elidew) {
+					first = st::mentionFont->m.elidedText(first + second, Qt::ElideRight, htagwidth);
 					second = QString();
 				} else {
-					second = st::botCommandFont->m.elidedText(second, Qt::ElideRight, htagwidth - firstwidth);
+					second = st::mentionFont->m.elidedText(second, Qt::ElideRight, htagwidth - firstwidth);
 				}
 			}
-			p.setFont(st::botCommandFont->f);
+			p.setFont(st::mentionFont->f);
 			if (!first.isEmpty()) {
 				p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
 				p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
@@ -2529,26 +2529,13 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
 			addleft += firstwidth + secondwidth + st::mentionPadding.left();
 			widthleft -= firstwidth + secondwidth + st::mentionPadding.left();
 
-			QString params = command.params;
-			if (widthleft > st::mentionFont->elidew && !params.isEmpty()) {
-				p.setFont(st::mentionFont->f);
-				int32 paramswidth = st::mentionFont->m.width(params);
-				if (widthleft < paramswidth) {
-					params = st::mentionFont->m.elidedText(params, Qt::ElideRight, widthleft);
-				}
-				p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
-				p.drawText(htagleft + addleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, params);
-
-				addleft += paramswidth + st::mentionPadding.left();
-				widthleft -= paramswidth + st::mentionPadding.left();
-			}
 			QString description = command.description;
-			if (widthleft > st::botDescFont->elidew && !description.isEmpty()) {
-				p.setFont(st::botDescFont->f);
-				int32 descwidth = st::botDescFont->m.width(description);
+			if (widthleft > st::mentionFont->elidew && !description.isEmpty()) {
+				p.setFont(st::mentionFont->f);
+				int32 descwidth = st::mentionFont->m.width(description);
 				if (widthleft < descwidth) {
-					description = st::botDescFont->m.elidedText(description, Qt::ElideRight, widthleft);
-					descwidth = st::botDescFont->m.width(description);
+					description = st::mentionFont->m.elidedText(description, Qt::ElideRight, widthleft);
+					descwidth = st::mentionFont->m.width(description);
 				}
 				p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
 				p.drawText(htagleft + addleft + (widthleft - descwidth), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, description);
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 7210d0664..c12c7b341 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -821,24 +821,27 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
 			}
 		}
 		if (adding->hasReplyMarkup()) {
-			if (peer->chat) {
-				peer->asChat()->markupSenders.insert(adding->from(), true);
-			}
-			if (App::replyMarkup(adding->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide
-				if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->chat && !adding->out())) {
+			int32 markupFlags = App::replyMarkup(adding->id).flags;
+			if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_personal) || adding->notifyByFrom()) {
+				if (peer->chat) {
+					peer->asChat()->markupSenders.insert(adding->from(), true);
+				}
+				if (markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide
+					if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->chat && !adding->out())) {
+						lastKeyboardInited = true;
+						lastKeyboardId = 0;
+						lastKeyboardFrom = 0;
+					}
+				} else if (peer->chat && (peer->asChat()->count < 1 || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from())) {
 					lastKeyboardInited = true;
 					lastKeyboardId = 0;
 					lastKeyboardFrom = 0;
+				} else {
+					lastKeyboardInited = true;
+					lastKeyboardId = adding->id;
+					lastKeyboardFrom = adding->from()->id;
+					lastKeyboardUsed = false;
 				}
-			} else if (peer->chat && (peer->asChat()->count < 1 || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from())) {
-				lastKeyboardInited = true;
-				lastKeyboardId = 0;
-				lastKeyboardFrom = 0;
-			} else {
-				lastKeyboardInited = true;
-				lastKeyboardId = adding->id;
-				lastKeyboardFrom = adding->from()->id;
-				lastKeyboardUsed = false;
 			}
 		}
 	}
@@ -949,34 +952,40 @@ void History::addToFront(const QVector<MTPMessage> &slice) {
 							lastAuthors->push_back(item->from());
 						}
 						if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // chats with bots
-							bool wasKeyboardHide = peer->asChat()->markupSenders.contains(item->from());
-							if (!wasKeyboardHide) {
-								peer->asChat()->markupSenders.insert(item->from(), true);
-							}
-							if (!(App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO)) {
-								if (!lastKeyboardInited) {
-									lastKeyboardInited = true;
-									if (wasKeyboardHide || ((peer->asChat()->count < 1 || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(item->from()))) {
-										lastKeyboardId = 0;
-										lastKeyboardFrom = 0;
-									} else {
-										lastKeyboardId = item->id;
-										lastKeyboardFrom = item->from()->id;
-										lastKeyboardUsed = false;
+							int32 markupFlags = App::replyMarkup(item->id).flags;
+							if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_personal) || item->notifyByFrom()) {
+								bool wasKeyboardHide = peer->asChat()->markupSenders.contains(item->from());
+								if (!wasKeyboardHide) {
+									peer->asChat()->markupSenders.insert(item->from(), true);
+								}
+								if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO)) {
+									if (!lastKeyboardInited) {
+										lastKeyboardInited = true;
+										if (wasKeyboardHide || ((peer->asChat()->count < 1 || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(item->from()))) {
+											lastKeyboardId = 0;
+											lastKeyboardFrom = 0;
+										} else {
+											lastKeyboardId = item->id;
+											lastKeyboardFrom = item->from()->id;
+											lastKeyboardUsed = false;
+										}
 									}
 								}
 							}
 						}
 					} else if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // conversations with bots
-						lastKeyboardInited = true;
-						if (App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) {
-							lastKeyboardId = 0;
-							lastKeyboardFrom = 0;
-						} else {
+						int32 markupFlags = App::replyMarkup(item->id).flags;
+						if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_personal) || item->notifyByFrom()) {
 							lastKeyboardInited = true;
-							lastKeyboardId = item->id;
-							lastKeyboardFrom = item->from()->id;
-							lastKeyboardUsed = false;
+							if (markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO) {
+								lastKeyboardId = 0;
+								lastKeyboardFrom = 0;
+							} else {
+								lastKeyboardInited = true;
+								lastKeyboardId = item->id;
+								lastKeyboardFrom = item->from()->id;
+								lastKeyboardUsed = false;
+							}
 						}
 					}
 				}
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index 3b155be68..8515732fa 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -1449,7 +1449,7 @@ void MessageField::focusInEvent(QFocusEvent *e) {
 	emit focused();
 }
 
-BotKeyboard::BotKeyboard() : _wasForMsgId(0), _height(0), _maxOuterHeight(0), _maximizeSize(false), _singleUse(false),
+BotKeyboard::BotKeyboard() : _wasForMsgId(0), _height(0), _maxOuterHeight(0), _maximizeSize(false), _singleUse(false), _forceReply(false),
 _sel(-1), _down(-1), _hoverAnim(animFunc(this, &BotKeyboard::hoverStep)), _st(&st::botKbButton) {
 	setGeometry(0, 0, _st->margin, _st->margin);
 	_height = _st->margin;
@@ -1579,6 +1579,7 @@ bool BotKeyboard::updateMarkup(HistoryItem *to) {
 		clearSelection();
 		_btns.clear();
 		const ReplyMarkup &markup(App::replyMarkup(to->id));
+		_forceReply = markup.flags | MTPDreplyKeyboardMarkup_flag_FORCE_REPLY;
 		_maximizeSize = !(markup.flags & MTPDreplyKeyboardMarkup_flag_resize);
 		_singleUse = markup.flags & MTPDreplyKeyboardMarkup_flag_single_use;
 
@@ -1609,7 +1610,7 @@ bool BotKeyboard::updateMarkup(HistoryItem *to) {
 		return true;
 	}
 	if (_wasForMsgId) {
-		_maximizeSize = _singleUse = false;
+		_maximizeSize = _singleUse = _forceReply = false;
 		_wasForMsgId = 0;
 		clearSelection();
 		_btns.clear();
@@ -1622,6 +1623,10 @@ bool BotKeyboard::hasMarkup() const {
 	return !_btns.isEmpty();
 }
 
+bool BotKeyboard::forceReply() const {
+	return _forceReply;
+}
+
 bool BotKeyboard::hoverStep(float64 ms) {
 	uint64 now = getms();
 	for (Animations::iterator i = _animations.begin(); i != _animations.end();) {
@@ -2279,8 +2284,8 @@ void HistoryWidget::onRecordError() {
 void HistoryWidget::onRecordDone(QByteArray result, qint32 samples) {
 	App::wnd()->activateWindow();
 	int32 duration = samples / AudioVoiceMsgFrequency;
-	imageLoader.append(result, duration, histPeer->id, _replyToId, ToPrepareAudio);
-	cancelReply();
+	imageLoader.append(result, duration, histPeer->id, replyToId(), ToPrepareAudio);
+	cancelReply(lastForceReplyReplied());
 }
 
 void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) {
@@ -2562,7 +2567,7 @@ bool HistoryWidget::kbWasHidden() {
 }
 
 void HistoryWidget::setKbWasHidden() {
-	if (_kbWasHidden || !_keyboard.hasMarkup()) return;
+	if (_kbWasHidden || (!_keyboard.hasMarkup() && !_keyboard.forceReply())) return;
 
 	_kbWasHidden = true;
 	if (!_showAnim.animating()) {
@@ -2876,11 +2881,16 @@ void HistoryWidget::updateControlsVisibility() {
 						_attachEmoji.hide();
 						_kbHide.show();
 						_kbShow.hide();
+					} else if (_kbReplyTo) {
+						_kbScroll.hide();
+						_attachEmoji.show();
+						_kbHide.hide();
+						_kbShow.hide();
 					} else {
 						_kbScroll.hide();
 						_attachEmoji.show();
 						_kbHide.hide();
-						if (_keyboard.hasMarkup()) {
+						if (_keyboard.hasMarkup() || _keyboard.forceReply()) {
 							_kbShow.show();
 						} else {
 							_kbShow.hide();
@@ -3275,6 +3285,7 @@ void HistoryWidget::onHistoryToEnd() {
 void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
 	if (!hist) return;
 
+	bool lastKeyboardUsed = lastForceReplyReplied(replyTo);
 	QString text = prepareMessage(_field.getLastText());
 	if (!text.isEmpty()) {
 		App::main()->readServerHistory(hist, false);
@@ -3297,7 +3308,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
 
 		App::main()->finishForwarding(hist);
 	}
-	if (replyTo < 0) cancelReply();
+	if (replyTo < 0) cancelReply(lastKeyboardUsed);
 	if (_previewData && _previewData->pendingTill) previewCancel();
 	_field.setFocus();
 }
@@ -3315,13 +3326,13 @@ void HistoryWidget::onBotStart() {
 		MTP::send(MTPmessages_StartBot(histPeer->asUser()->inputUser, MTP_int(0), MTP_long(randomId), MTP_string(token)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, histPeer->asUser()));
 
 		histPeer->asUser()->botInfo->startToken = QString();
-		if (_keyboard.hasMarkup()) {
+		if (_keyboard.hasMarkup() || _keyboard.forceReply()) {
 			if (_keyboard.singleUse() && _keyboard.forMsgId() == hist->lastKeyboardId && hist->lastKeyboardUsed) _kbWasHidden = true;
-			if (!_kbWasHidden) _kbShown = true;
+			if (!_kbWasHidden) _kbShown = _keyboard.hasMarkup();
 		}
-		updateControlsVisibility();
-		resizeEvent(0);
 	}
+	updateControlsVisibility();
+	resizeEvent(0);
 }
 
 void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) {
@@ -3330,7 +3341,7 @@ void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) {
 	App::main()->showPeer(peer, 0, false, true);
 	if (!hist) return;
 
-	shareContact(peer, contact->phone, contact->firstName, contact->lastName, _replyToId, int32(contact->id & 0xFFFFFFFF));
+	shareContact(peer, contact->phone, contact->firstName, contact->lastName, replyToId(), int32(contact->id & 0xFFFFFFFF));
 }
 
 void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) {
@@ -3345,18 +3356,20 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const
 	PeerData *p = App::peer(peer);
 	int32 flags = newMessageFlags(p); // unread, out
 	
+	bool lastKeyboardUsed = lastForceReplyReplied(replyTo);
+
 	int32 sendFlags = 0;
 	if (replyTo) {
 		flags |= MTPDmessage::flag_reply_to_msg_id;
 		sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id;
 	}
-	h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(peer), MTPint(), MTPint(), MTP_int(_replyToId), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)), MTPnullMarkup));
+	h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(peer), MTPint(), MTPint(), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)), MTPnullMarkup));
 	h->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
 
 	App::historyRegRandom(randomId, newId);
 
 	App::main()->finishForwarding(h);
-	cancelReply();
+	cancelReply(lastKeyboardUsed);
 }
 
 void HistoryWidget::onSendPaths(const PeerId &peer) {
@@ -3580,7 +3593,7 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
 	QPoint pos(e ? e->pos() : mapFromGlobal(QCursor::pos()));
 	bool inRecord = _send.geometry().contains(pos);
 	bool inField = pos.y() >= (_scroll.y() + _scroll.height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width();
-	bool inReply = QRect(st::replySkip, _field.y() - st::sendPadding - st::replyHeight, width() - st::replySkip - _replyForwardPreviewCancel.width(), st::replyHeight).contains(pos) && (_replyToId || _kbReplyTo);
+	bool inReply = QRect(st::replySkip, _field.y() - st::sendPadding - st::replyHeight, width() - st::replySkip - _replyForwardPreviewCancel.width(), st::replyHeight).contains(pos) && replyToId();
 	bool startAnim = false;
 	if (inRecord != _inRecord) {
 		_inRecord = inRecord;
@@ -3646,6 +3659,8 @@ void HistoryWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { // reply
 	App::main()->readServerHistory(hist, false);
 	hist->loadAround(0);
 
+	bool lastKeyboardUsed = (_keyboard.forMsgId() == hist->lastKeyboardId) && (_keyboard.forMsgId() == replyTo);
+
 	QString toSend = cmd;
 	UserData *bot = histPeer->chat ? (App::hoveredLinkItem() ? (App::hoveredLinkItem()->toHistoryForwarded() ? App::hoveredLinkItem()->toHistoryForwarded()->fromForwarded() : App::hoveredLinkItem()->from()) : 0) : histPeer->asUser();
 	QString username = (bot && bot->botInfo) ? bot->username : QString();
@@ -3657,7 +3672,7 @@ void HistoryWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { // reply
 	App::main()->sendPreparedText(hist, toSend, replyTo ? ((histPeer->chat/* && (botStatus == 0 || botStatus == 2)*/) ? replyTo : -1) : 0);
 	if (replyTo) {
 		cancelReply();
-		if (_keyboard.hasMarkup() && _keyboard.singleUse() && _keyboard.forMsgId() == replyTo) {
+		if (_keyboard.singleUse() && _keyboard.hasMarkup() && lastKeyboardUsed) {
 			if (_kbShown) onKbToggle(false);
 			hist->lastKeyboardUsed = true;
 		}
@@ -3788,7 +3803,7 @@ void HistoryWidget::onDocumentDrop(QDropEvent *e) {
 }
 
 void HistoryWidget::onKbToggle(bool manual) {
-	if (_kbShown) {
+	if (_kbShown || _kbReplyTo) {
 		_kbHide.hide();
 		_kbShow.show();
 		_kbScroll.hide();
@@ -3801,6 +3816,21 @@ void HistoryWidget::onKbToggle(bool manual) {
 			_replyForwardPreviewCancel.hide();
 		}
 		if (manual) _kbWasHidden = true;
+	} else if (!_keyboard.hasMarkup() && _keyboard.forceReply()) {
+		_kbHide.hide();
+		_kbShow.hide();
+		_kbScroll.hide();
+		_kbShown = false;
+
+		_field.setMaxHeight(st::maxFieldHeight);
+
+		_kbReplyTo = hist->peer->chat ? App::histItemById(_keyboard.forMsgId()) : 0;
+		if (_kbReplyTo && !_replyToId) {
+			updateReplyToName();
+			_replyToText.setText(st::msgFont, _kbReplyTo->inDialogsText(), _textDlgOptions);
+			_replyForwardPreviewCancel.show();
+		}
+		if (manual) _kbWasHidden = false;
 	} else {
 		_kbHide.show();
 		_kbShow.hide();
@@ -4038,7 +4068,7 @@ void HistoryWidget::uploadImage(const QImage &img, bool withText, const QString
 	confirmImage = img;
 	confirmWithText = withText;
 	confirmSource = source;
-	confirmImageId = imageLoader.append(img, histPeer->id, _replyToId, ToPreparePhoto);
+	confirmImageId = imageLoader.append(img, histPeer->id, replyToId(), ToPreparePhoto);
 }
 
 void HistoryWidget::uploadFile(const QString &file, bool withText) {
@@ -4046,7 +4076,7 @@ void HistoryWidget::uploadFile(const QString &file, bool withText) {
 
 	App::wnd()->activateWindow();
 	confirmWithText = withText;
-	confirmImageId = imageLoader.append(file, histPeer->id, _replyToId, ToPrepareDocument);
+	confirmImageId = imageLoader.append(file, histPeer->id, replyToId(), ToPrepareDocument);
 }
 
 void HistoryWidget::shareContactConfirmation(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool withText) {
@@ -4066,27 +4096,28 @@ void HistoryWidget::uploadConfirmImageUncompressed(bool ctrlShiftEnter, MsgId re
 	if (confirmWithText) {
 		onSend(ctrlShiftEnter, replyTo);
 	}
+	bool lastKeyboardUsed = lastForceReplyReplied(replyTo);
 	imageLoader.append(confirmImage, peerId, replyTo, ToPrepareDocument, ctrlShiftEnter);
 	confirmImageId = 0;
 	confirmWithText = false;
 	confirmImage = QImage();
-	cancelReply();
+	cancelReply(lastKeyboardUsed);
 }
 
 void HistoryWidget::uploadMedias(const QStringList &files, ToPrepareMediaType type) {
 	if (!hist) return;
 
 	App::wnd()->activateWindow();
-	imageLoader.append(files, histPeer->id, _replyToId, type);
-	cancelReply();
+	imageLoader.append(files, histPeer->id, replyToId(), type);
+	cancelReply(lastForceReplyReplied());
 }
 
 void HistoryWidget::uploadMedia(const QByteArray &fileContent, ToPrepareMediaType type, PeerId peer) {
 	if (!peer && !hist) return;
 
 	App::wnd()->activateWindow();
-	imageLoader.append(fileContent, peer ? peer : histPeer->id, _replyToId, type);
-	cancelReply();
+	imageLoader.append(fileContent, peer ? peer : histPeer->id, replyToId(), type);
+	cancelReply(lastForceReplyReplied());
 }
 
 void HistoryWidget::onPhotoReady() {
@@ -4360,7 +4391,7 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) {
 	_replyForwardPreviewCancel.move(width() - _replyForwardPreviewCancel.width(), _field.y() - st::sendPadding - _replyForwardPreviewCancel.height());
 	updateListSize();
 
-	_field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width() - ((_kbShown || !_keyboard.hasMarkup()) ? 0 : _kbShow.width()), _field.height());
+	_field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width() - ((_kbShown || (!_keyboard.hasMarkup() && !_keyboard.forceReply())) ? 0 : _kbShow.width()), _field.height());
 
 	_toHistoryEnd.move((width() - _toHistoryEnd.width()) / 2, _scroll.y() + _scroll.height() - _toHistoryEnd.height() - st::historyToEndSkip);
 
@@ -4418,7 +4449,7 @@ void HistoryWidget::updateScrollColors() {
 }
 
 MsgId HistoryWidget::replyToId() const {
-	return _replyToId;
+	return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
 }
 
 void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, HistoryItem *resizedItem, bool scrollToIt) {
@@ -4441,7 +4472,7 @@ void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown,
 		if (hist->readyForWork() && (!histPeer->chat || !histPeer->asChat()->forbidden)) {
 			newScrollHeight -= (_field.height() + 2 * st::sendPadding);
 		}
-		if (_replyToId || _kbReplyTo || App::main()->hasForwardingItems() || (_previewData && _previewData->pendingTill >= 0)) {
+		if (replyToId() || App::main()->hasForwardingItems() || (_previewData && _previewData->pendingTill >= 0)) {
 			newScrollHeight -= st::replyHeight;
 		}
 		if (_kbShown) {
@@ -4536,7 +4567,7 @@ void HistoryWidget::addMessagesToBack(const QVector<MTPMessage> &messages) {
 
 void HistoryWidget::updateBotKeyboard() {
 	bool changed = false;
-	bool wasVisible = _kbShown;
+	bool wasVisible = _kbShown || _kbReplyTo;
 	if ((_replyToId && !_replyTo) || !hist) {
 		changed = _keyboard.updateMarkup(0);
 	} else if (_replyTo) {
@@ -4546,18 +4577,25 @@ void HistoryWidget::updateBotKeyboard() {
 	}
 	if (!changed) return;
 
-	if (_keyboard.hasMarkup()) {
+	bool hasMarkup = _keyboard.hasMarkup(), forceReply = _keyboard.forceReply() && !_replyTo;
+	if (hasMarkup || forceReply) {
 		if (_keyboard.singleUse() && _keyboard.forMsgId() == hist->lastKeyboardId && hist->lastKeyboardUsed) _kbWasHidden = true;
 		if (!isBotStart() && (wasVisible || _replyTo || (_field.getLastText().isEmpty() && !_kbWasHidden))) {
 			if (!_showAnim.animating()) {
-				_kbScroll.show();
-				_attachEmoji.hide();
-				_kbHide.show();
+				if (hasMarkup) {
+					_kbScroll.show();
+					_attachEmoji.hide();
+					_kbHide.show();
+				} else {
+					_kbScroll.hide();
+					_attachEmoji.show();
+					_kbHide.hide();
+				}
 				_kbShow.hide();
 			}
-			int32 maxh = qMin(_keyboard.height(), int(st::maxFieldHeight) - (int(st::maxFieldHeight) / 2));
+			int32 maxh = hasMarkup ? qMin(_keyboard.height(), int(st::maxFieldHeight) - (int(st::maxFieldHeight) / 2)) : 0;
 			_field.setMaxHeight(st::maxFieldHeight - maxh);
-			_kbShown = true;
+			_kbShown = hasMarkup;
 			_kbReplyTo = hist->peer->chat ? App::histItemById(_keyboard.forMsgId()) : 0;
 			if (_kbReplyTo && !_replyToId) {
 				updateReplyToName();
@@ -4622,7 +4660,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
 		a_recordOver.restart();
 		_recordAnim.start();
 	} else if (_inReply) {
-		App::main()->showPeer(histPeer->id, _replyToId ? _replyToId : _kbReplyTo->id);
+		App::main()->showPeer(histPeer->id, replyToId());
 	}
 }
 
@@ -4693,18 +4731,20 @@ void HistoryWidget::onStickerSend(DocumentData *sticker) {
 
 	hist->loadAround(0);
 
+	bool lastKeyboardUsed = lastForceReplyReplied();
+
 	bool out = (histPeer->input.type() != mtpc_inputPeerSelf), unread = (histPeer->input.type() != mtpc_inputPeerSelf);
 	int32 flags = newMessageFlags(histPeer); // unread, out
 	int32 sendFlags = 0;
-	if (_replyToId) {
+	if (replyToId()) {
 		flags |= MTPDmessage::flag_reply_to_msg_id;
 		sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id;
 	}
-	hist->addToBackDocument(newId, flags, _replyToId, date(MTP_int(unixtime())), MTP::authedId(), sticker);
+	hist->addToBackDocument(newId, flags, replyToId(), date(MTP_int(unixtime())), MTP::authedId(), sticker);
 
-	hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), histPeer->input, MTP_int(_replyToId), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
+	hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), histPeer->input, MTP_int(replyToId()), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
 	App::main()->finishForwarding(hist);
-	cancelReply();
+	cancelReply(lastKeyboardUsed);
 
 	if (sticker->sticker) App::main()->incrementSticker(sticker);
 
@@ -4759,23 +4799,32 @@ void HistoryWidget::onReplyToMessage() {
 	_field.setFocus();
 }
 
-void HistoryWidget::cancelReply() {
-	if (!_replyToId) return;
-	_replyTo = 0;
-	_replyToId = 0;
-	mouseMoveEvent(0);
-	if (!App::main()->hasForwardingItems() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
-		_replyForwardPreviewCancel.hide();
+bool HistoryWidget::lastForceReplyReplied(MsgId replyTo) const {
+	return _keyboard.forceReply() && _keyboard.forMsgId() == hist->lastKeyboardId && _keyboard.forMsgId() == (replyTo < 0 ? replyToId() : replyTo);
+}
+
+void HistoryWidget::cancelReply(bool lastKeyboardUsed) {
+	if (_replyToId) {
+		_replyTo = 0;
+		_replyToId = 0;
+		mouseMoveEvent(0);
+		if (!App::main()->hasForwardingItems() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
+			_replyForwardPreviewCancel.hide();
+		}
+
+		updateBotKeyboard();
+
+		resizeEvent(0);
+		update();
+
+		_saveDraftText = true;
+		_saveDraftStart = getms();
+		onDraftSave();
+	}
+	if (_keyboard.singleUse() && _keyboard.forceReply() && lastKeyboardUsed) {
+		if (_kbReplyTo) onKbToggle(false);
+		hist->lastKeyboardUsed = true;
 	}
-
-	updateBotKeyboard();
-
-	resizeEvent(0);
-	update();
-
-	_saveDraftText = true;
-	_saveDraftStart = getms();
-	onDraftSave();
 }
 
 void HistoryWidget::cancelForwarding() {
@@ -4912,7 +4961,7 @@ void HistoryWidget::updatePreview() {
 			_previewTitle.setText(st::msgServiceNameFont, title, _textNameOptions);
 			_previewDescription.setText(st::msgFont, desc, _textDlgOptions);
 		}
-	} else if (!App::main()->hasForwardingItems() && !_replyToId && !_kbReplyTo) {
+	} else if (!App::main()->hasForwardingItems() && !replyToId()) {
 		_replyForwardPreviewCancel.hide();
 	}
 	resizeEvent(0);
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 58868d4d6..2725941c1 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -214,6 +214,7 @@ public:
 
 	bool updateMarkup(HistoryItem *last);
 	bool hasMarkup() const;
+	bool forceReply() const;
 
 	bool hoverStep(float64 ms);
 	void resizeToWidth(int32 width, int32 maxOuterHeight);
@@ -237,7 +238,7 @@ private:
 
 	MsgId _wasForMsgId;
 	int32 _height, _maxOuterHeight;
-	bool _maximizeSize, _singleUse;
+	bool _maximizeSize, _singleUse, _forceReply;
 	QTimer _cmdTipTimer;
 
 	QPoint _lastMousePos;
@@ -430,7 +431,8 @@ public:
 
 	MsgId replyToId() const;
 	void updateReplyTo(bool force = false);
-	void cancelReply();
+	bool lastForceReplyReplied(MsgId replyTo = -1) const;
+	void cancelReply(bool lastKeyboardUsed = false);
 	void updateForwarding(bool force = false);
 	void cancelForwarding(); // called by MainWidget
 
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index c891aff76..a0a4e3acf 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -1567,13 +1567,13 @@ void MainWidget::confirmShareContact(bool ctrlShiftEnter, const QString &phone,
 }
 
 void MainWidget::confirmSendImage(const ReadyLocalMedia &img) {
+	bool lastKeyboardUsed = history.lastForceReplyReplied(img.replyTo);
 	history.confirmSendImage(img);
-	history.cancelReply();
+	history.cancelReply(lastKeyboardUsed);
 }
 
 void MainWidget::confirmSendImageUncompressed(bool ctrlShiftEnter, MsgId replyTo) {
 	history.uploadConfirmImageUncompressed(ctrlShiftEnter, replyTo);
-	history.cancelReply();
 }
 
 void MainWidget::cancelSendImage() {
diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.h b/Telegram/SourceFiles/mtproto/mtpConnection.h
index fc29b511f..ac41f9f73 100644
--- a/Telegram/SourceFiles/mtproto/mtpConnection.h
+++ b/Telegram/SourceFiles/mtproto/mtpConnection.h
@@ -41,6 +41,8 @@ enum {
 
 	MTPDreplyKeyboardMarkup_flag_resize = (1 << 0),
 	MTPDreplyKeyboardMarkup_flag_single_use = (1 << 1),
+	MTPDreplyKeyboardMarkup_flag_personal = (1 << 2),
+	MTPDreplyKeyboardMarkup_flag_FORCE_REPLY = (1 << 30), // client side flag for forceReply
 	MTPDreplyKeyboardMarkup_flag_ZERO = (1 << 31) // client side flag for zeroMarkup
 };
 
diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp
index 74e7e58c8..2ff60009d 100644
--- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp
@@ -4294,8 +4294,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
 				}
 				switch (stage) {
 				case 0: to.add("  command: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-				case 1: to.add("  params: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-				case 2: to.add("  description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+				case 1: to.add("  description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
 				default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
 				}
 			break;
@@ -4348,7 +4347,29 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
 			break;
 
 			case mtpc_replyKeyboardHide:
-				to.add("{ replyKeyboardHide }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
+				if (stage) {
+					to.add(",\n").addSpaces(lev);
+				} else {
+					to.add("{ replyKeyboardHide");
+					to.add("\n").addSpaces(lev);
+				}
+				switch (stage) {
+				case 0: to.add("  flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+				default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
+				}
+			break;
+
+			case mtpc_replyKeyboardForceReply:
+				if (stage) {
+					to.add(",\n").addSpaces(lev);
+				} else {
+					to.add("{ replyKeyboardForceReply");
+					to.add("\n").addSpaces(lev);
+				}
+				switch (stage) {
+				case 0: to.add("  flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+				default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
+				}
 			break;
 
 			case mtpc_replyKeyboardMarkup:
diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h
index 5ad499233..2acf51b10 100644
--- a/Telegram/SourceFiles/mtproto/mtpScheme.h
+++ b/Telegram/SourceFiles/mtproto/mtpScheme.h
@@ -380,12 +380,13 @@ enum {
 	mtpc_stickerSet = 0xa7a43b17,
 	mtpc_messages_stickerSet = 0xb60a24a6,
 	mtpc_user = 0x22e49072,
-	mtpc_botCommand = 0xb79d22ab,
+	mtpc_botCommand = 0xc27ac8c7,
 	mtpc_botInfoEmpty = 0xbb2e37ce,
 	mtpc_botInfo = 0x9cf585d,
 	mtpc_keyboardButton = 0xa2fa4880,
 	mtpc_keyboardButtonRow = 0x77608b83,
-	mtpc_replyKeyboardHide = 0xced6ebbc,
+	mtpc_replyKeyboardHide = 0xa03e5b85,
+	mtpc_replyKeyboardForceReply = 0xf4108aa0,
 	mtpc_replyKeyboardMarkup = 0x3502758c,
 	mtpc_invokeAfterMsg = 0xcb9f372d,
 	mtpc_invokeAfterMsgs = 0x3dc4b4f0,
@@ -1078,6 +1079,8 @@ class MTPkeyboardButtonRow;
 class MTPDkeyboardButtonRow;
 
 class MTPreplyMarkup;
+class MTPDreplyKeyboardHide;
+class MTPDreplyKeyboardForceReply;
 class MTPDreplyKeyboardMarkup;
 
 
@@ -7878,7 +7881,7 @@ public:
 private:
 	explicit MTPbotCommand(MTPDbotCommand *_data);
 
-	friend MTPbotCommand MTP_botCommand(const MTPstring &_command, const MTPstring &_params, const MTPstring &_description);
+	friend MTPbotCommand MTP_botCommand(const MTPstring &_command, const MTPstring &_description);
 };
 typedef MTPBoxed<MTPbotCommand> MTPBotCommand;
 
@@ -7990,6 +7993,30 @@ public:
 		read(from, end, cons);
 	}
 
+	MTPDreplyKeyboardHide &_replyKeyboardHide() {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_replyKeyboardHide) throw mtpErrorWrongTypeId(_type, mtpc_replyKeyboardHide);
+		split();
+		return *(MTPDreplyKeyboardHide*)data;
+	}
+	const MTPDreplyKeyboardHide &c_replyKeyboardHide() const {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_replyKeyboardHide) throw mtpErrorWrongTypeId(_type, mtpc_replyKeyboardHide);
+		return *(const MTPDreplyKeyboardHide*)data;
+	}
+
+	MTPDreplyKeyboardForceReply &_replyKeyboardForceReply() {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_replyKeyboardForceReply) throw mtpErrorWrongTypeId(_type, mtpc_replyKeyboardForceReply);
+		split();
+		return *(MTPDreplyKeyboardForceReply*)data;
+	}
+	const MTPDreplyKeyboardForceReply &c_replyKeyboardForceReply() const {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_replyKeyboardForceReply) throw mtpErrorWrongTypeId(_type, mtpc_replyKeyboardForceReply);
+		return *(const MTPDreplyKeyboardForceReply*)data;
+	}
+
 	MTPDreplyKeyboardMarkup &_replyKeyboardMarkup() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_replyKeyboardMarkup) throw mtpErrorWrongTypeId(_type, mtpc_replyKeyboardMarkup);
@@ -8011,9 +8038,12 @@ public:
 
 private:
 	explicit MTPreplyMarkup(mtpTypeId type);
+	explicit MTPreplyMarkup(MTPDreplyKeyboardHide *_data);
+	explicit MTPreplyMarkup(MTPDreplyKeyboardForceReply *_data);
 	explicit MTPreplyMarkup(MTPDreplyKeyboardMarkup *_data);
 
-	friend MTPreplyMarkup MTP_replyKeyboardHide();
+	friend MTPreplyMarkup MTP_replyKeyboardHide(MTPint _flags);
+	friend MTPreplyMarkup MTP_replyKeyboardForceReply(MTPint _flags);
 	friend MTPreplyMarkup MTP_replyKeyboardMarkup(MTPint _flags, const MTPVector<MTPKeyboardButtonRow> &_rows);
 
 	mtpTypeId _type;
@@ -11224,11 +11254,10 @@ class MTPDbotCommand : public mtpDataImpl<MTPDbotCommand> {
 public:
 	MTPDbotCommand() {
 	}
-	MTPDbotCommand(const MTPstring &_command, const MTPstring &_params, const MTPstring &_description) : vcommand(_command), vparams(_params), vdescription(_description) {
+	MTPDbotCommand(const MTPstring &_command, const MTPstring &_description) : vcommand(_command), vdescription(_description) {
 	}
 
 	MTPstring vcommand;
-	MTPstring vparams;
 	MTPstring vdescription;
 };
 
@@ -11266,6 +11295,26 @@ public:
 	MTPVector<MTPKeyboardButton> vbuttons;
 };
 
+class MTPDreplyKeyboardHide : public mtpDataImpl<MTPDreplyKeyboardHide> {
+public:
+	MTPDreplyKeyboardHide() {
+	}
+	MTPDreplyKeyboardHide(MTPint _flags) : vflags(_flags) {
+	}
+
+	MTPint vflags;
+};
+
+class MTPDreplyKeyboardForceReply : public mtpDataImpl<MTPDreplyKeyboardForceReply> {
+public:
+	MTPDreplyKeyboardForceReply() {
+	}
+	MTPDreplyKeyboardForceReply(MTPint _flags) : vflags(_flags) {
+	}
+
+	MTPint vflags;
+};
+
 class MTPDreplyKeyboardMarkup : public mtpDataImpl<MTPDreplyKeyboardMarkup> {
 public:
 	MTPDreplyKeyboardMarkup() {
@@ -25792,7 +25841,7 @@ inline MTPbotCommand::MTPbotCommand() : mtpDataOwner(new MTPDbotCommand()) {
 
 inline uint32 MTPbotCommand::innerLength() const {
 	const MTPDbotCommand &v(c_botCommand());
-	return v.vcommand.innerLength() + v.vparams.innerLength() + v.vdescription.innerLength();
+	return v.vcommand.innerLength() + v.vdescription.innerLength();
 }
 inline mtpTypeId MTPbotCommand::type() const {
 	return mtpc_botCommand;
@@ -25803,19 +25852,17 @@ inline void MTPbotCommand::read(const mtpPrime *&from, const mtpPrime *end, mtpT
 	if (!data) setData(new MTPDbotCommand());
 	MTPDbotCommand &v(_botCommand());
 	v.vcommand.read(from, end);
-	v.vparams.read(from, end);
 	v.vdescription.read(from, end);
 }
 inline void MTPbotCommand::write(mtpBuffer &to) const {
 	const MTPDbotCommand &v(c_botCommand());
 	v.vcommand.write(to);
-	v.vparams.write(to);
 	v.vdescription.write(to);
 }
 inline MTPbotCommand::MTPbotCommand(MTPDbotCommand *_data) : mtpDataOwner(_data) {
 }
-inline MTPbotCommand MTP_botCommand(const MTPstring &_command, const MTPstring &_params, const MTPstring &_description) {
-	return MTPbotCommand(new MTPDbotCommand(_command, _params, _description));
+inline MTPbotCommand MTP_botCommand(const MTPstring &_command, const MTPstring &_description) {
+	return MTPbotCommand(new MTPDbotCommand(_command, _description));
 }
 
 inline uint32 MTPbotInfo::innerLength() const {
@@ -25931,6 +25978,14 @@ inline MTPkeyboardButtonRow MTP_keyboardButtonRow(const MTPVector<MTPKeyboardBut
 
 inline uint32 MTPreplyMarkup::innerLength() const {
 	switch (_type) {
+		case mtpc_replyKeyboardHide: {
+			const MTPDreplyKeyboardHide &v(c_replyKeyboardHide());
+			return v.vflags.innerLength();
+		}
+		case mtpc_replyKeyboardForceReply: {
+			const MTPDreplyKeyboardForceReply &v(c_replyKeyboardForceReply());
+			return v.vflags.innerLength();
+		}
 		case mtpc_replyKeyboardMarkup: {
 			const MTPDreplyKeyboardMarkup &v(c_replyKeyboardMarkup());
 			return v.vflags.innerLength() + v.vrows.innerLength();
@@ -25945,7 +26000,16 @@ inline mtpTypeId MTPreplyMarkup::type() const {
 inline void MTPreplyMarkup::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
 	if (cons != _type) setData(0);
 	switch (cons) {
-		case mtpc_replyKeyboardHide: _type = cons; break;
+		case mtpc_replyKeyboardHide: _type = cons; {
+			if (!data) setData(new MTPDreplyKeyboardHide());
+			MTPDreplyKeyboardHide &v(_replyKeyboardHide());
+			v.vflags.read(from, end);
+		} break;
+		case mtpc_replyKeyboardForceReply: _type = cons; {
+			if (!data) setData(new MTPDreplyKeyboardForceReply());
+			MTPDreplyKeyboardForceReply &v(_replyKeyboardForceReply());
+			v.vflags.read(from, end);
+		} break;
 		case mtpc_replyKeyboardMarkup: _type = cons; {
 			if (!data) setData(new MTPDreplyKeyboardMarkup());
 			MTPDreplyKeyboardMarkup &v(_replyKeyboardMarkup());
@@ -25957,6 +26021,14 @@ inline void MTPreplyMarkup::read(const mtpPrime *&from, const mtpPrime *end, mtp
 }
 inline void MTPreplyMarkup::write(mtpBuffer &to) const {
 	switch (_type) {
+		case mtpc_replyKeyboardHide: {
+			const MTPDreplyKeyboardHide &v(c_replyKeyboardHide());
+			v.vflags.write(to);
+		} break;
+		case mtpc_replyKeyboardForceReply: {
+			const MTPDreplyKeyboardForceReply &v(c_replyKeyboardForceReply());
+			v.vflags.write(to);
+		} break;
 		case mtpc_replyKeyboardMarkup: {
 			const MTPDreplyKeyboardMarkup &v(c_replyKeyboardMarkup());
 			v.vflags.write(to);
@@ -25966,15 +26038,23 @@ inline void MTPreplyMarkup::write(mtpBuffer &to) const {
 }
 inline MTPreplyMarkup::MTPreplyMarkup(mtpTypeId type) : mtpDataOwner(0), _type(type) {
 	switch (type) {
-		case mtpc_replyKeyboardHide: break;
+		case mtpc_replyKeyboardHide: setData(new MTPDreplyKeyboardHide()); break;
+		case mtpc_replyKeyboardForceReply: setData(new MTPDreplyKeyboardForceReply()); break;
 		case mtpc_replyKeyboardMarkup: setData(new MTPDreplyKeyboardMarkup()); break;
 		default: throw mtpErrorBadTypeId(type, "MTPreplyMarkup");
 	}
 }
+inline MTPreplyMarkup::MTPreplyMarkup(MTPDreplyKeyboardHide *_data) : mtpDataOwner(_data), _type(mtpc_replyKeyboardHide) {
+}
+inline MTPreplyMarkup::MTPreplyMarkup(MTPDreplyKeyboardForceReply *_data) : mtpDataOwner(_data), _type(mtpc_replyKeyboardForceReply) {
+}
 inline MTPreplyMarkup::MTPreplyMarkup(MTPDreplyKeyboardMarkup *_data) : mtpDataOwner(_data), _type(mtpc_replyKeyboardMarkup) {
 }
-inline MTPreplyMarkup MTP_replyKeyboardHide() {
-	return MTPreplyMarkup(mtpc_replyKeyboardHide);
+inline MTPreplyMarkup MTP_replyKeyboardHide(MTPint _flags) {
+	return MTPreplyMarkup(new MTPDreplyKeyboardHide(_flags));
+}
+inline MTPreplyMarkup MTP_replyKeyboardForceReply(MTPint _flags) {
+	return MTPreplyMarkup(new MTPDreplyKeyboardForceReply(_flags));
 }
 inline MTPreplyMarkup MTP_replyKeyboardMarkup(MTPint _flags, const MTPVector<MTPKeyboardButtonRow> &_rows) {
 	return MTPreplyMarkup(new MTPDreplyKeyboardMarkup(_flags, _rows));
diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl
index f424e8608..793d6d595 100644
--- a/Telegram/SourceFiles/mtproto/scheme.tl
+++ b/Telegram/SourceFiles/mtproto/scheme.tl
@@ -594,7 +594,7 @@ messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:
 
 user#22e49072 flags:# id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int = User;
 
-botCommand#b79d22ab command:string params:string description:string = BotCommand;
+botCommand#c27ac8c7 command:string description:string = BotCommand;
 
 botInfoEmpty#bb2e37ce = BotInfo;
 botInfo#9cf585d user_id:int version:int share_text:string description:string commands:Vector<BotCommand> = BotInfo;
@@ -603,7 +603,8 @@ keyboardButton#a2fa4880 text:string = KeyboardButton;
 
 keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
 
-replyKeyboardHide#ced6ebbc = ReplyMarkup;
+replyKeyboardHide#a03e5b85 flags:# = ReplyMarkup;
+replyKeyboardForceReply#f4108aa0 flags:# = ReplyMarkup;
 replyKeyboardMarkup#3502758c flags:# rows:Vector<KeyboardButtonRow> = ReplyMarkup;
 
 ---functions---
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index 7b8de002c..791e35f20 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -244,7 +244,7 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
 		botInfo->commands.reserve(v.size());
 		for (int32 i = 0, l = v.size(); i < l; ++i) {
 			if (v.at(i).type() == mtpc_botCommand) {
-				botInfo->commands.push_back(BotCommand(qs(v.at(i).c_botCommand().vcommand), qs(v.at(i).c_botCommand().vparams), qs(v.at(i).c_botCommand().vdescription)));
+				botInfo->commands.push_back(BotCommand(qs(v.at(i).c_botCommand().vcommand), qs(v.at(i).c_botCommand().vdescription)));
 			}
 		}
 
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 363c1af3c..72f3f2f66 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -119,9 +119,9 @@ private:
 };
 
 struct BotCommand {
-	BotCommand(const QString &command, const QString &params, const QString &description) : command(command), params(params), description(description) {
+	BotCommand(const QString &command, const QString &description) : command(command), description(description) {
 	}
-	QString command, params, description;
+	QString command, description;
 };
 struct BotInfo {
 	BotInfo() : inited(false), readsAllHistory(false), cantJoinGroups(false), version(0), text(st::msgMinWidth) {
diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist
index d05afdb15..6a96f9bfb 100644
--- a/Telegram/Telegram.plist
+++ b/Telegram/Telegram.plist
@@ -11,7 +11,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.8.28</string>
+	<string>0.8.29</string>
         <key>LSMinimumSystemVersion</key>
         <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
 	<key>CFBundleSignature</key>
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index 061f78a85..598b2eb1c 100644
Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ
diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj
index 49c556759..dd2793ed6 100644
--- a/Telegram/Telegram.xcodeproj/project.pbxproj
+++ b/Telegram/Telegram.xcodeproj/project.pbxproj
@@ -1701,7 +1701,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.28;
+				CURRENT_PROJECT_VERSION = 0.8.29;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -1719,7 +1719,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = YES;
-				CURRENT_PROJECT_VERSION = 0.8.28;
+				CURRENT_PROJECT_VERSION = 0.8.29;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_OPTIMIZATION_LEVEL = fast;
 				GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@@ -1745,10 +1745,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.28;
+				CURRENT_PROJECT_VERSION = 0.8.29;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DYLIB_COMPATIBILITY_VERSION = 0.8;
-				DYLIB_CURRENT_VERSION = 0.8.28;
+				DYLIB_CURRENT_VERSION = 0.8.29;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1888,10 +1888,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.8.28;
+				CURRENT_PROJECT_VERSION = 0.8.29;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DYLIB_COMPATIBILITY_VERSION = 0.8;
-				DYLIB_CURRENT_VERSION = 0.8.28;
+				DYLIB_CURRENT_VERSION = 0.8.29;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
diff --git a/Telegram/Version.sh b/Telegram/Version.sh
index b405bce4c..057ea19a4 100755
--- a/Telegram/Version.sh
+++ b/Telegram/Version.sh
@@ -1,2 +1,2 @@
-echo 8028 0.8.28 1
+echo 8029 0.8.29 1
 # AppVersion AppVersionStr DevChannel