From 4240568ea5406e4e83564e918500f9ea0b0f5079 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 10 Oct 2023 10:49:22 +0400
Subject: [PATCH] Pass FullReplyTo everywhere.

---
 Telegram/SourceFiles/api/api_bot.cpp          |  18 +-
 Telegram/SourceFiles/api/api_common.cpp       |   6 +-
 Telegram/SourceFiles/api/api_polls.cpp        |   2 +-
 Telegram/SourceFiles/api/api_sending.cpp      |   4 +-
 Telegram/SourceFiles/apiwrap.cpp              |  23 +-
 .../boxes/reactions_settings_box.cpp          |   8 +-
 .../SourceFiles/chat_helpers/bot_command.h    |   2 +-
 .../SourceFiles/core/click_handler_types.cpp  |   1 -
 Telegram/SourceFiles/data/data_drafts.cpp     |  45 +-
 Telegram/SourceFiles/data/data_drafts.h       |  16 +-
 Telegram/SourceFiles/data/data_histories.cpp  |  42 +-
 Telegram/SourceFiles/data/data_histories.h    |  21 +-
 Telegram/SourceFiles/data/data_msg_id.h       |  32 +-
 Telegram/SourceFiles/dialogs/dialogs_key.h    |   3 +-
 .../admin_log/history_admin_log_inner.cpp     |   2 +-
 .../admin_log/history_admin_log_inner.h       |   2 +-
 Telegram/SourceFiles/history/history.cpp      |  19 +-
 Telegram/SourceFiles/history/history.h        |   6 +-
 .../history/history_inner_widget.cpp          |   6 +-
 .../history/history_inner_widget.h            |   2 +-
 Telegram/SourceFiles/history/history_item.cpp | 236 +++++------
 Telegram/SourceFiles/history/history_item.h   |   9 +-
 .../history/history_item_components.cpp       | 399 ++++++++++++------
 .../history/history_item_components.h         | 103 +++--
 .../history/history_item_helpers.cpp          |  43 +-
 .../history/history_item_helpers.h            |   6 +-
 .../SourceFiles/history/history_widget.cpp    | 248 ++++++-----
 Telegram/SourceFiles/history/history_widget.h |  14 +-
 .../history_view_compose_controls.cpp         | 109 ++---
 .../controls/history_view_compose_controls.h  |   5 +-
 .../controls/history_view_forward_panel.cpp   |   4 +-
 .../history/view/history_view_bottom_info.cpp |   2 +-
 .../view/history_view_context_menu.cpp        |   2 +-
 .../history/view/history_view_element.cpp     |  14 +-
 .../history/view/history_view_element.h       |   4 +-
 .../history/view/history_view_list_widget.cpp |  14 +-
 .../history/view/history_view_list_widget.h   |   8 +-
 .../history/view/history_view_message.cpp     |  23 +-
 .../view/history_view_replies_section.cpp     |  33 +-
 .../view/history_view_replies_section.h       |   4 +-
 .../view/history_view_translate_tracker.cpp   |   2 +-
 .../history/view/media/history_view_gif.cpp   |   4 +-
 .../media/history_view_media_unwrapped.cpp    |   9 +-
 Telegram/SourceFiles/mainwidget.cpp           |   7 +-
 Telegram/SourceFiles/mainwidget.h             |   2 +-
 .../media/view/media_view_overlay_widget.cpp  |   2 +-
 .../SourceFiles/storage/storage_account.cpp   |  88 ++--
 .../SourceFiles/storage/storage_account.h     |   2 +-
 .../SourceFiles/support/support_helper.cpp    |   3 +-
 .../window/notifications_manager.cpp          |  12 +-
 .../SourceFiles/window/window_peer_menu.cpp   |  24 +-
 .../SourceFiles/window/window_peer_menu.h     |   3 +-
 .../window/window_session_controller.cpp      |  10 +-
 53 files changed, 964 insertions(+), 744 deletions(-)

diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp
index 65826aa2f..b45474fb5 100644
--- a/Telegram/SourceFiles/api/api_bot.cpp
+++ b/Telegram/SourceFiles/api/api_bot.cpp
@@ -169,9 +169,7 @@ void SendBotCallbackData(
 void HideSingleUseKeyboard(
 		not_null<Window::SessionController*> controller,
 		not_null<HistoryItem*> item) {
-	controller->content()->hideSingleUseKeyboard(
-		item->history()->peer,
-		item->id);
+	controller->content()->hideSingleUseKeyboard(item->fullId());
 }
 
 } // namespace
@@ -312,7 +310,9 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
 	case ButtonType::Default: {
 		// Copy string before passing it to the sending method
 		// because the original button can be destroyed inside.
-		const auto replyTo = item->isRegular() ? item->id : 0;
+		const auto replyTo = item->isRegular()
+			? item->fullId()
+			: FullMsgId();
 		controller->content()->sendBotCommand({
 			.peer = item->history()->peer,
 			.command = QString(button->text),
@@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
 
 	case ButtonType::RequestPhone: {
 		HideSingleUseKeyboard(controller, item);
-		const auto itemId = item->id;
+		const auto itemId = item->fullId();
 		const auto topicRootId = item->topicRootId();
 		const auto history = item->history();
 		controller->show(Ui::MakeConfirmBox({
@@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
 				auto action = Api::SendAction(history);
 				action.clearDraft = false;
 				action.replyTo = {
-					.msgId = itemId,
+					.messageId = itemId,
 					.topicRootId = topicRootId,
 				};
 				history->session().api().shareContact(
@@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
 				chosen |= PollData::Flag::Quiz;
 			}
 		}
-		const auto replyToId = MsgId(0);
-		const auto topicRootId = MsgId(0);
+		const auto replyTo = FullReplyTo();
 		Window::PeerMenuCreatePoll(
 			controller,
 			item->history()->peer,
-			replyToId,
-			topicRootId,
+			replyTo,
 			chosen,
 			disabled);
 	} break;
diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp
index 0a44a916f..cfb1e7220 100644
--- a/Telegram/SourceFiles/api/api_common.cpp
+++ b/Telegram/SourceFiles/api/api_common.cpp
@@ -19,8 +19,8 @@ SendAction::SendAction(
 	SendOptions options)
 : history(thread->owningHistory())
 , options(options)
-, replyTo({ .msgId = thread->topicRootId() }) {
-	replyTo.topicRootId = replyTo.msgId;
+, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) {
+	replyTo.topicRootId = replyTo.messageId.msg;
 }
 
 SendOptions DefaultSendWhenOnlineOptions() {
@@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() {
 }
 
 MTPInputReplyTo SendAction::mtpReplyTo() const {
-	return Data::ReplyToForMTP(&history->owner(), replyTo);
+	return Data::ReplyToForMTP(history, replyTo);
 }
 
 } // namespace Api
diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp
index 227b6e9db..7699894cf 100644
--- a/Telegram/SourceFiles/api/api_polls.cpp
+++ b/Telegram/SourceFiles/api/api_polls.cpp
@@ -43,7 +43,7 @@ void Polls::create(
 
 	const auto history = action.history;
 	const auto peer = history->peer;
-	const auto topicRootId = action.replyTo.msgId
+	const auto topicRootId = action.replyTo.messageId
 		? action.replyTo.topicRootId
 		: 0;
 	auto sendFlags = MTPmessages_SendMedia::Flags(0);
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 291a2675a..09001ae35 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -368,9 +368,9 @@ void SendConfirmedFile(
 
 	if (!isEditing) {
 		const auto histories = &session->data().histories();
-		file->to.replyTo.msgId = histories->convertTopicReplyToId(
+		file->to.replyTo.messageId = histories->convertTopicReplyToId(
 			history,
-			file->to.replyTo.msgId);
+			file->to.replyTo.messageId);
 		file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
 			history,
 			file->to.replyTo.topicRootId);
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index affdd5524..006a99d51 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -2134,16 +2134,12 @@ void ApiWrap::saveDraftsToCloud() {
 		}
 
 		auto flags = MTPmessages_SaveDraft::Flags(0);
-		auto replyFlags = MTPDinputReplyToMessage::Flags(0);
 		auto &textWithTags = cloudDraft->textWithTags;
 		if (cloudDraft->previewState != Data::PreviewState::Allowed) {
 			flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
 		}
-		if (cloudDraft->msgId || cloudDraft->topicRootId) {
+		if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
 			flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
-			if (cloudDraft->topicRootId) {
-				replyFlags |= MTPDinputReplyToMessage::Flag::f_top_msg_id;
-			}
 		}
 		if (!textWithTags.tags.isEmpty()) {
 			flags |= MTPmessages_SaveDraft::Flag::f_entities;
@@ -2156,15 +2152,7 @@ void ApiWrap::saveDraftsToCloud() {
 		history->startSavingCloudDraft(topicRootId);
 		cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
 			MTP_flags(flags),
-			MTP_inputReplyToMessage(
-				MTP_flags(replyFlags),
-				MTP_int(cloudDraft->msgId
-					? cloudDraft->msgId
-					: cloudDraft->topicRootId),
-				MTP_int(cloudDraft->topicRootId),
-				MTPInputPeer(), // reply_to_peer_id
-				MTPstring(), // quote_text
-				MTPVector<MTPMessageEntity>()), // quote_entities
+			ReplyToForMTP(history, cloudDraft->reply),
 			history->peer->input,
 			MTP_string(textWithTags.text),
 			entities
@@ -3586,9 +3574,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
 	action.generateLocal = true;
 	sendAction(action);
 
-	const auto replyToId = action.replyTo.msgId;
-	const auto replyTo = replyToId
-		? peer->owner().message(peer, replyToId)
+	const auto replyTo = action.replyTo.messageId
+		? peer->owner().message(action.replyTo.messageId)
 		: nullptr;
 	const auto topicRootId = replyTo
 		? replyTo->topicRootId()
@@ -3789,7 +3776,7 @@ void ApiWrap::sendInlineResult(
 			? (*localMessageId)
 			: _session->data().nextLocalMessageId());
 	const auto randomId = base::RandomValue<uint64>();
-	const auto topicRootId = action.replyTo.msgId
+	const auto topicRootId = action.replyTo.messageId
 		? action.replyTo.topicRootId
 		: 0;
 
diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
index f0282306b..9b375e4c8 100644
--- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
+++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
@@ -71,7 +71,7 @@ AdminLog::OwnedItem GenerateItem(
 		not_null<HistoryView::ElementDelegate*> delegate,
 		not_null<History*> history,
 		PeerId from,
-		MsgId replyTo,
+		FullMsgId replyTo,
 		const QString &text) {
 	Expects(history->peer->isUser());
 
@@ -81,7 +81,7 @@ AdminLog::OwnedItem GenerateItem(
 			| MessageFlag::HasFromId
 			| MessageFlag::HasReplyInfo),
 		UserId(), // via
-		FullReplyTo{ .msgId = replyTo },
+		FullReplyTo{ .messageId = replyTo },
 		base::unixtime::now(), // date
 		from,
 		QString(), // postAuthor
@@ -143,13 +143,13 @@ void AddMessage(
 		GenerateUser(
 			history,
 			tr::lng_settings_chat_message_reply_from(tr::now)),
-		0,
+		FullMsgId(),
 		tr::lng_settings_chat_message_reply(tr::now));
 	auto message = GenerateItem(
 		state->delegate.get(),
 		history,
 		history->peer->id,
-		state->reply->data()->fullId().msg,
+		state->reply->data()->fullId(),
 		tr::lng_settings_chat_message(tr::now));
 	const auto view = message.get();
 	state->item = std::move(message);
diff --git a/Telegram/SourceFiles/chat_helpers/bot_command.h b/Telegram/SourceFiles/chat_helpers/bot_command.h
index d5e0c9511..b91ef6903 100644
--- a/Telegram/SourceFiles/chat_helpers/bot_command.h
+++ b/Telegram/SourceFiles/chat_helpers/bot_command.h
@@ -16,7 +16,7 @@ struct SendCommandRequest {
 	not_null<PeerData*> peer;
 	QString command;
 	FullMsgId context;
-	MsgId replyTo = 0;
+	FullReplyTo replyTo;
 };
 
 [[nodiscard]] QString WrapCommandInChat(
diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp
index e3bbba7d1..f79661971 100644
--- a/Telegram/SourceFiles/core/click_handler_types.cpp
+++ b/Telegram/SourceFiles/core/click_handler_types.cpp
@@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
 			.peer = peer,
 			.command = _cmd,
 			.context = my.itemId,
-			.replyTo = 0,
 		});
 	}
 }
diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp
index f0b937ca8..807af9404 100644
--- a/Telegram/SourceFiles/data/data_drafts.cpp
+++ b/Telegram/SourceFiles/data/data_drafts.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "chat_helpers/message_field.h"
 #include "history/history.h"
 #include "history/history_widget.h"
+#include "history/history_item_components.h"
 #include "main/main_session.h"
 #include "data/data_session.h"
 #include "mainwidget.h"
@@ -21,14 +22,12 @@ namespace Data {
 
 Draft::Draft(
 	const TextWithTags &textWithTags,
-	MsgId msgId,
-	MsgId topicRootId,
+	FullReplyTo reply,
 	const MessageCursor &cursor,
 	PreviewState previewState,
 	mtpRequestId saveRequestId)
 : textWithTags(textWithTags)
-, msgId(msgId)
-, topicRootId(topicRootId)
+, reply(std::move(reply))
 , cursor(cursor)
 , previewState(previewState)
 , saveRequestId(saveRequestId) {
@@ -36,13 +35,11 @@ Draft::Draft(
 
 Draft::Draft(
 	not_null<const Ui::InputField*> field,
-	MsgId msgId,
-	MsgId topicRootId,
+	FullReplyTo reply,
 	PreviewState previewState,
 	mtpRequestId saveRequestId)
 : textWithTags(field->getTextWithTags())
-, msgId(msgId)
-, topicRootId(topicRootId)
+, reply(std::move(reply))
 , cursor(field)
 , previewState(previewState) {
 }
@@ -64,22 +61,26 @@ void ApplyPeerCloudDraft(
 				session,
 				draft.ventities().value_or_empty()))
 	};
-	auto replyTo = MsgId();
-	if (const auto reply = draft.vreply_to()) {
-		reply->match([&](const MTPDmessageReplyHeader &data) {
-			if (!data.vreply_to_peer_id()
-				|| (peerFromMTP(*data.vreply_to_peer_id()) == peerId)) {
-				replyTo = data.vreply_to_msg_id().value_or_empty();
-			} else {
-				// #TODO replies
-			}
-		}, [&](const MTPDmessageReplyStoryHeader &data) {
-		});
-	}
+	const auto reply = draft.vreply_to()
+		? ReplyFieldsFromMTP(history, *draft.vreply_to())
+		: ReplyFields();
+	const auto replyPeerId = reply.externalPeerId
+		? reply.externalPeerId
+		: peerId;
 	auto cloudDraft = std::make_unique<Draft>(
 		textWithTags,
-		replyTo,
-		topicRootId,
+		FullReplyTo{
+			.messageId = FullMsgId(replyPeerId, reply.messageId),
+			.quote = TextWithTags{
+				reply.quote.text,
+				TextUtilities::ConvertEntitiesToTextTags(
+					reply.quote.entities),
+			},
+			.storyId = (reply.storyId
+				? FullStoryId(replyPeerId, reply.storyId)
+				: FullStoryId()),
+			.topicRootId = topicRootId,
+		},
 		MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
 		(draft.is_no_webpage()
 			? Data::PreviewState::Cancelled
diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h
index 5af044019..ef295807c 100644
--- a/Telegram/SourceFiles/data/data_drafts.h
+++ b/Telegram/SourceFiles/data/data_drafts.h
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "data/data_msg_id.h"
+
 namespace Ui {
 class InputField;
 } // namespace Ui
@@ -38,22 +40,19 @@ struct Draft {
 	Draft() = default;
 	Draft(
 		const TextWithTags &textWithTags,
-		MsgId msgId,
-		MsgId topicRootId,
+		FullReplyTo reply,
 		const MessageCursor &cursor,
 		PreviewState previewState,
 		mtpRequestId saveRequestId = 0);
 	Draft(
 		not_null<const Ui::InputField*> field,
-		MsgId msgId,
-		MsgId topicRootId,
+		FullReplyTo reply,
 		PreviewState previewState,
 		mtpRequestId saveRequestId = 0);
 
 	TimeId date = 0;
 	TextWithTags textWithTags;
-	MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft
-	MsgId topicRootId = 0;
+	FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft.
 	MessageCursor cursor;
 	PreviewState previewState = PreviewState::Allowed;
 	mtpRequestId saveRequestId = 0;
@@ -167,7 +166,8 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
 
 [[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
 	return !draft
-		|| (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text));
+		|| (!draft->reply.messageId
+			&& DraftStringIsEmpty(draft->textWithTags.text));
 }
 
 [[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
@@ -179,7 +179,7 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
 		return false;
 	}
 	return (a->textWithTags == b->textWithTags)
-		&& (a->msgId == b->msgId)
+		&& (a->reply == b->reply)
 		&& (a->previewState == b->previewState);
 }
 
diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp
index 7443aab62..ab002d24f 100644
--- a/Telegram/SourceFiles/data/data_histories.cpp
+++ b/Telegram/SourceFiles/data/data_histories.cpp
@@ -33,8 +33,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
 } // namespace
 
 MTPInputReplyTo ReplyToForMTP(
-		not_null<Session*> owner,
+		not_null<History*> history,
 		FullReplyTo replyTo) {
+	const auto owner = &history->owner();
 	if (replyTo.storyId) {
 		if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
 			if (const auto user = peer->asUser()) {
@@ -43,15 +44,19 @@ MTPInputReplyTo ReplyToForMTP(
 					MTP_int(replyTo.storyId.story));
 			}
 		}
-	} else if (replyTo.msgId || replyTo.topicRootId) {
+	} else if (replyTo.messageId || replyTo.topicRootId) {
+		const auto external = (replyTo.messageId.peer != history->peer->id);
 		using Flag = MTPDinputReplyToMessage::Flag;
-		return MTP_inputReplyToMessage( // #TODO replies
-			(replyTo.topicRootId
-				? MTP_flags(Flag::f_top_msg_id)
-				: MTP_flags(0)),
-			MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId),
+		return MTP_inputReplyToMessage(
+			MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
+				| (external ? Flag::f_reply_to_peer_id : Flag())),
+			MTP_int(replyTo.messageId
+				? replyTo.messageId.msg
+				: replyTo.topicRootId),
 			MTP_int(replyTo.topicRootId),
-			MTPInputPeer(), // reply_to_peer_id
+			(external
+				? owner->peer(replyTo.messageId.peer)->input
+				: MTPInputPeer()),
 			MTPstring(), // quote_text
 			MTPVector<MTPMessageEntity>()); // quote_entities
 	}
@@ -914,7 +919,7 @@ int Histories::sendPreparedMessage(
 		not_null<History*> history,
 		FullReplyTo replyTo,
 		uint64 randomId,
-		Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message,
+		Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
 		Fn<void(const MTPUpdates&, const MTP::Response&)> done,
 		Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
 	if (isCreatingTopic(history, replyTo.topicRootId)) {
@@ -929,7 +934,7 @@ int Histories::sendPreparedMessage(
 		}
 		i->second.push_back({
 			.randomId = randomId,
-			.replyTo = replyTo.msgId,
+			.replyTo = replyTo.messageId,
 			.message = std::move(message),
 			.done = std::move(done),
 			.fail = std::move(fail),
@@ -939,11 +944,11 @@ int Histories::sendPreparedMessage(
 		return id;
 	}
 	const auto realReplyTo = FullReplyTo{
-		.msgId = convertTopicReplyToId(history, replyTo.msgId),
-		.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
+		.messageId = convertTopicReplyToId(history, replyTo.messageId),
 		.storyId = replyTo.storyId,
+		.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
 	};
-	return v::match(message(_owner, realReplyTo), [&](const auto &request) {
+	return v::match(message(history, realReplyTo), [&](const auto &request) {
 		const auto type = RequestType::Send;
 		return sendRequest(history, type, [=](Fn<void()> finish) {
 			const auto session = &_owner->session();
@@ -987,7 +992,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
 			sendPreparedMessage(
 				history,
 				FullReplyTo{
-					.msgId = entry.replyTo,
+					.messageId = entry.replyTo,
 					.topicRootId = realRoot,
 				},
 				entry.randomId,
@@ -1009,6 +1014,15 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
 	}
 }
 
+FullMsgId Histories::convertTopicReplyToId(
+		not_null<History*> history,
+		FullMsgId replyToId) const {
+	const auto id = (history->peer->id == replyToId.peer)
+		? convertTopicReplyToId(history, replyToId.msg)
+		: replyToId.msg;
+	return { replyToId.peer, id };
+}
+
 MsgId Histories::convertTopicReplyToId(
 		not_null<History*> history,
 		MsgId replyToId) const {
diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h
index c98dc352c..336dcffc3 100644
--- a/Telegram/SourceFiles/data/data_histories.h
+++ b/Telegram/SourceFiles/data/data_histories.h
@@ -27,7 +27,7 @@ class Session;
 class Folder;
 
 [[nodiscard]] MTPInputReplyTo ReplyToForMTP(
-	not_null<Session*> owner,
+	not_null<History*> history,
 	FullReplyTo replyTo);
 
 class Histories final {
@@ -108,7 +108,7 @@ public:
 		not_null<History*> history,
 		FullReplyTo replyTo,
 		uint64 randomId,
-		Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message,
+		Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
 		Fn<void(const MTPUpdates&, const MTP::Response&)> done,
 		Fn<void(const MTP::Error&, const MTP::Response&)> fail);
 
@@ -116,14 +116,17 @@ public:
 	};
 	template <typename RequestType, typename ...Args>
 	static auto PrepareMessage(const Args &...args)
-	-> Fn<Histories::PreparedMessage(not_null<Session*>, FullReplyTo)> {
-		return [=](not_null<Session*> owner, FullReplyTo replyTo)
+	-> Fn<Histories::PreparedMessage(not_null<History*>, FullReplyTo)> {
+		return [=](not_null<History*> history, FullReplyTo replyTo)
 		-> RequestType {
-			return { ReplaceReplyIds(owner, args, replyTo)... };
+			return { ReplaceReplyIds(history, args, replyTo)... };
 		};
 	}
 
 	void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
+	[[nodiscard]] FullMsgId convertTopicReplyToId(
+		not_null<History*> history,
+		FullMsgId replyToId) const;
 	[[nodiscard]] MsgId convertTopicReplyToId(
 		not_null<History*> history,
 		MsgId replyToId) const;
@@ -152,8 +155,8 @@ private:
 	};
 	struct DelayedByTopicMessage {
 		uint64 randomId = 0;
-		MsgId replyTo = 0;
-		Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message;
+		FullMsgId replyTo;
+		Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message;
 		Fn<void(const MTPUpdates&, const MTP::Response&)> done;
 		Fn<void(const MTP::Error&, const MTP::Response&)> fail;
 		int requestId = 0;
@@ -169,11 +172,11 @@ private:
 
 	template <typename Arg>
 	static auto ReplaceReplyIds(
-			not_null<Session*> owner,
+			not_null<History*> history,
 			Arg arg,
 			FullReplyTo replyTo) {
 		if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
-			return ReplyToForMTP(owner, replyTo);
+			return ReplyToForMTP(history, replyTo);
 		} else {
 			return arg;
 		}
diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h
index 6a9bc0d1c..5d367d83e 100644
--- a/Telegram/SourceFiles/data/data_msg_id.h
+++ b/Telegram/SourceFiles/data/data_msg_id.h
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "data/data_peer_id.h"
+#include "ui/text/text_entity.h"
 
 struct MsgId {
 	constexpr MsgId() noexcept = default;
@@ -67,21 +68,6 @@ struct FullStoryId {
 	friend inline bool operator==(FullStoryId, FullStoryId) = default;
 };
 
-struct FullReplyTo {
-	MsgId msgId = 0;
-	MsgId topicRootId = 0;
-	FullStoryId storyId;
-
-	[[nodiscard]] bool valid() const {
-		return msgId || (storyId && peerIsUser(storyId.peer));
-	}
-	explicit operator bool() const {
-		return valid();
-	}
-	friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
-	friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
-};
-
 constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
 constexpr auto ClientMsgIds = (1LL << 31);
 constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds);
@@ -169,6 +155,22 @@ struct FullMsgId {
 
 Q_DECLARE_METATYPE(FullMsgId);
 
+struct FullReplyTo {
+	FullMsgId messageId;
+	TextWithTags quote;
+	FullStoryId storyId;
+	MsgId topicRootId = 0;
+
+	[[nodiscard]] bool valid() const {
+		return messageId || (storyId && peerIsUser(storyId.peer));
+	}
+	explicit operator bool() const {
+		return valid();
+	}
+	friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
+	friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
+};
+
 struct GlobalMsgId {
 	FullMsgId itemId;
 	uint64 sessionUniqueId = 0;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h
index 4493cb5e1..d542cfca3 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.h
@@ -108,8 +108,7 @@ struct EntryState {
 	Key key;
 	Section section = Section::History;
 	FilterId filterId = 0;
-	MsgId rootId = 0;
-	MsgId currentReplyToId = 0;
+	FullReplyTo currentReplyTo;
 
 	friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept
 		= default;
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 2b49f8c1c..deb786a28 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -659,7 +659,7 @@ not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {
 	return _pathGradient.get();
 }
 
-void InnerWidget::elementReplyTo(const FullMsgId &to) {
+void InnerWidget::elementReplyTo(const FullReplyTo &to) {
 }
 
 void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index 684fba84f..36c321810 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -127,7 +127,7 @@ public:
 	void elementHandleViaClick(not_null<UserData*> bot) override;
 	bool elementIsChatWide() override;
 	not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
-	void elementReplyTo(const FullMsgId &to) override;
+	void elementReplyTo(const FullReplyTo &to) override;
 	void elementStartInteraction(
 		not_null<const HistoryView::Element*> view) override;
 	void elementStartPremium(
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 6ff35aea5..adafbc39b 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -177,8 +177,7 @@ void History::takeLocalDraft(not_null<History*> from) {
 		&& !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
 		// Edit and reply to drafts can't migrate.
 		// Cloud drafts do not migrate automatically.
-		draft->msgId = 0;
-
+		draft->reply = FullReplyTo();
 		setLocalDraft(std::move(draft));
 	}
 	from->clearLocalDraft(topicRootId);
@@ -194,6 +193,7 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
 		return;
 	}
 
+	draft->reply.topicRootId = topicRootId;
 	auto existing = localDraft(topicRootId);
 	if (Data::DraftIsNull(existing)
 		|| !existing->date
@@ -201,15 +201,13 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
 		if (!existing) {
 			setLocalDraft(std::make_unique<Data::Draft>(
 				draft->textWithTags,
-				draft->msgId,
-				topicRootId,
+				draft->reply,
 				draft->cursor,
 				draft->previewState));
 			existing = localDraft(topicRootId);
 		} else if (existing != draft) {
 			existing->textWithTags = draft->textWithTags;
-			existing->msgId = draft->msgId;
-			existing->topicRootId = draft->topicRootId;
+			existing->reply = draft->reply;
 			existing->cursor = draft->cursor;
 			existing->previewState = draft->previewState;
 		}
@@ -277,8 +275,7 @@ Data::Draft *History::createCloudDraft(
 	if (Data::DraftIsNull(fromDraft)) {
 		setCloudDraft(std::make_unique<Data::Draft>(
 			TextWithTags(),
-			0,
-			topicRootId,
+			FullReplyTo(),
 			MessageCursor(),
 			Data::PreviewState::Allowed));
 		cloudDraft(topicRootId)->date = TimeId(0);
@@ -287,18 +284,18 @@ Data::Draft *History::createCloudDraft(
 		if (!existing) {
 			setCloudDraft(std::make_unique<Data::Draft>(
 				fromDraft->textWithTags,
-				fromDraft->msgId,
-				topicRootId,
+				fromDraft->reply,
 				fromDraft->cursor,
 				fromDraft->previewState));
 			existing = cloudDraft(topicRootId);
 		} else if (existing != fromDraft) {
 			existing->textWithTags = fromDraft->textWithTags;
-			existing->msgId = fromDraft->msgId;
+			existing->reply = fromDraft->reply;
 			existing->cursor = fromDraft->cursor;
 			existing->previewState = fromDraft->previewState;
 		}
 		existing->date = base::unixtime::now();
+		existing->reply.topicRootId = topicRootId;
 	}
 
 	if (const auto thread = threadFor(topicRootId)) {
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 933832d79..2ef89cb46 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -329,17 +329,17 @@ public:
 	}
 	void setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
 		setDraft(
-			Data::DraftKey::Local(draft->topicRootId),
+			Data::DraftKey::Local(draft->reply.topicRootId),
 			std::move(draft));
 	}
 	void setLocalEditDraft(std::unique_ptr<Data::Draft> &&draft) {
 		setDraft(
-			Data::DraftKey::LocalEdit(draft->topicRootId),
+			Data::DraftKey::LocalEdit(draft->reply.topicRootId),
 			std::move(draft));
 	}
 	void setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
 		setDraft(
-			Data::DraftKey::Cloud(draft->topicRootId),
+			Data::DraftKey::Cloud(draft->reply.topicRootId),
 			std::move(draft));
 	}
 	void clearLocalDraft(MsgId topicRootId) {
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 23d3f1c4e..d4b3aaf46 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -287,7 +287,7 @@ public:
 
 		return _widget->elementPathShiftGradient();
 	}
-	void elementReplyTo(const FullMsgId &to) override {
+	void elementReplyTo(const FullReplyTo &to) override {
 		if (_widget) {
 			_widget->elementReplyTo(to);
 		}
@@ -2206,7 +2206,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 		}();
 		if (canReply) {
 			_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
-				_widget->replyToMessage(itemId);
+				_widget->replyToMessage({ itemId });
 			}, &st::menuIconReply);
 		}
 		const auto repliesCount = item->repliesCount();
@@ -3481,7 +3481,7 @@ not_null<Ui::PathShiftGradient*> HistoryInner::elementPathShiftGradient() {
 	return _pathGradient.get();
 }
 
-void HistoryInner::elementReplyTo(const FullMsgId &to) {
+void HistoryInner::elementReplyTo(const FullReplyTo &to) {
 	return _widget->replyToMessage(to);
 }
 
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index e6855ef92..38d68cdde 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -159,7 +159,7 @@ public:
 	void elementHandleViaClick(not_null<UserData*> bot);
 	bool elementIsChatWide();
 	not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
-	void elementReplyTo(const FullMsgId &to);
+	void elementReplyTo(const FullReplyTo &to);
 	void elementStartInteraction(not_null<const Element*> view);
 	void elementStartPremium(
 		not_null<const Element*> view,
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 0c106f19f..b1f1f1a61 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -125,23 +125,23 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
 }
 
 struct HistoryItem::CreateConfig {
-	PeerId replyToPeer = 0;
-	MsgId replyTo = 0;
-	MsgId replyToTop = 0;
-	StoryId replyToStory = 0;
-	bool replyIsTopicPost = false;
+	ReplyFields reply;
+
 	UserId viaBotId = 0;
 	int viewsCount = -1;
 	int forwardsCount = -1;
-	QString author;
-	PeerId senderOriginal = 0;
-	QString senderNameOriginal;
-	QString forwardPsaType;
+	QString postAuthor;
+
 	MsgId originalId = 0;
+	TimeId originalDate = 0;
+	PeerId originalSenderId = 0;
+	QString originalSenderName;
+	QString originalPostAuthor;
+
+	QString forwardPsaType;
 	PeerId savedFromPeer = 0;
 	MsgId savedFromMsgId = 0;
-	QString authorOriginal;
-	TimeId originalDate = 0;
+
 	TimeId editDate = 0;
 	HistoryMessageMarkupData markup;
 	HistoryMessageRepliesData replies;
@@ -154,14 +154,14 @@ struct HistoryItem::CreateConfig {
 void HistoryItem::FillForwardedInfo(
 		CreateConfig &config,
 		const MTPDmessageFwdHeader &data) {
-	if (const auto fromId = data.vfrom_id()) {
-		config.senderOriginal = peerFromMTP(*fromId);
-	}
-	config.originalDate = data.vdate().v;
-	config.senderNameOriginal = qs(data.vfrom_name().value_or_empty());
-	config.forwardPsaType = qs(data.vpsa_type().value_or_empty());
 	config.originalId = data.vchannel_post().value_or_empty();
-	config.authorOriginal = qs(data.vpost_author().value_or_empty());
+	config.originalDate = data.vdate().v;
+	if (const auto fromId = data.vfrom_id()) {
+		config.originalSenderId = peerFromMTP(*fromId);
+	}
+	config.originalSenderName = qs(data.vfrom_name().value_or_empty());
+	config.originalPostAuthor = qs(data.vpost_author().value_or_empty());
+	config.forwardPsaType = qs(data.vpsa_type().value_or_empty());
 	const auto savedFromPeer = data.vsaved_from_peer();
 	const auto savedFromMsgId = data.vsaved_from_msg_id();
 	if (savedFromPeer && savedFromMsgId) {
@@ -435,21 +435,21 @@ HistoryItem::HistoryItem(
 
 	const auto originalMedia = original->media();
 	const auto dropForwardInfo = original->computeDropForwardedInfo();
-	config.replyTo = config.replyToTop = topicRootId;
-	config.replyIsTopicPost = (topicRootId != 0);
+	config.reply.messageId = config.reply.topMessageId = topicRootId;
+	config.reply.topicPost = (topicRootId != 0);
 	if (!dropForwardInfo) {
-		config.originalDate = original->dateOriginal();
+		config.originalDate = original->originalDate();
 		if (const auto info = original->hiddenSenderInfo()) {
-			config.senderNameOriginal = info->name;
-		} else if (const auto senderOriginal = original->senderOriginal()) {
-			config.senderOriginal = senderOriginal->id;
-			if (senderOriginal->isChannel()) {
-				config.originalId = original->idOriginal();
+			config.originalSenderName = info->name;
+		} else if (const auto originalSender = original->originalSender()) {
+			config.originalSenderId = originalSender->id;
+			if (originalSender->isChannel()) {
+				config.originalId = original->originalId();
 			}
 		} else {
 			Unexpected("Corrupt forwarded information in message.");
 		}
-		config.authorOriginal = original->authorOriginal();
+		config.originalPostAuthor = original->originalPostAuthor();
 	}
 	if (peer->isSelf()) {
 		//
@@ -465,12 +465,12 @@ HistoryItem::HistoryItem(
 		//}
 	}
 	if (flags & MessageFlag::HasPostAuthor) {
-		config.author = postAuthor;
+		config.postAuthor = postAuthor;
 	}
 	if (const auto fwdViaBot = original->viaBot()) {
 		config.viaBotId = peerToUser(fwdViaBot->id);
 	} else if (originalMedia && originalMedia->game()) {
-		if (const auto sender = original->senderOriginal()) {
+		if (const auto sender = original->originalSender()) {
 			if (const auto user = sender->asUser()) {
 				if (user->isBot()) {
 					config.viaBotId = peerToUser(user->id);
@@ -482,8 +482,8 @@ HistoryItem::HistoryItem(
 	if (fwdViewsCount > 0) {
 		config.viewsCount = fwdViewsCount;
 	} else if ((isPost() && !isScheduled())
-		|| (original->senderOriginal()
-			&& original->senderOriginal()->isChannel())) {
+		|| (original->originalSender()
+			&& original->originalSender()->isChannel())) {
 		config.viewsCount = 1;
 	}
 
@@ -1026,7 +1026,7 @@ void HistoryItem::setCommentsItemId(FullMsgId id) {
 	if (id.peer == _history->peer->id) {
 		if (id.msg != this->id) {
 			if (const auto reply = Get<HistoryMessageReply>()) {
-				reply->replyToMsgTop = id.msg;
+				reply->setTopMessageId(id.msg);
 			}
 		}
 	} else if (const auto views = Get<HistoryMessageViews>()) {
@@ -1879,7 +1879,7 @@ void HistoryItem::changeReplyToTopCounter(
 			this,
 			Data::MessageUpdate::Flag::ReplyToTopAdded);
 	}
-	const auto topId = reply->replyToTop();
+	const auto topId = reply->topMessageId();
 	if (!topId) {
 		return;
 	}
@@ -1931,8 +1931,8 @@ void HistoryItem::setRealId(MsgId newId) {
 	_history->owner().requestItemResize(this);
 
 	if (const auto reply = Get<HistoryMessageReply>()) {
-		if (reply->replyToLink()) {
-			reply->setReplyToLinkFrom(this);
+		if (reply->link()) {
+			reply->setLinkFrom(this);
 		}
 		changeReplyToTopCounter(reply, 1);
 	}
@@ -2381,14 +2381,14 @@ not_null<PeerData*> HistoryItem::author() const {
 	return (isPost() && !isSponsored()) ? _history->peer : from();
 }
 
-TimeId HistoryItem::dateOriginal() const {
+TimeId HistoryItem::originalDate() const {
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 		return forwarded->originalDate;
 	}
 	return date();
 }
 
-PeerData *HistoryItem::senderOriginal() const {
+PeerData *HistoryItem::originalSender() const {
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 		return forwarded->originalSender;
 	}
@@ -2416,18 +2416,18 @@ not_null<PeerData*> HistoryItem::fromOriginal() const {
 	return from();
 }
 
-QString HistoryItem::authorOriginal() const {
+QString HistoryItem::originalPostAuthor() const {
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
-		return forwarded->originalAuthor;
+		return forwarded->originalPostAuthor;
 	} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
 		if (!msgsigned->isAnonymousRank) {
-			return msgsigned->author;
+			return msgsigned->postAuthor;
 		}
 	}
 	return QString();
 }
 
-MsgId HistoryItem::idOriginal() const {
+MsgId HistoryItem::originalId() const {
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 		return forwarded->originalId;
 	}
@@ -2491,9 +2491,9 @@ void HistoryItem::setForwardsCount(int count) {
 	history()->owner().notifyItemDataChange(this);
 }
 
-void HistoryItem::setPostAuthor(const QString &author) {
+void HistoryItem::setPostAuthor(const QString &postAuthor) {
 	auto msgsigned = Get<HistoryMessageSigned>();
-	if (author.isEmpty()) {
+	if (postAuthor.isEmpty()) {
 		if (!msgsigned) {
 			return;
 		}
@@ -2504,10 +2504,10 @@ void HistoryItem::setPostAuthor(const QString &author) {
 	if (!msgsigned) {
 		AddComponents(HistoryMessageSigned::Bit());
 		msgsigned = Get<HistoryMessageSigned>();
-	} else if (msgsigned->author == author) {
+	} else if (msgsigned->postAuthor == postAuthor) {
 		return;
 	}
-	msgsigned->author = author;
+	msgsigned->postAuthor = postAuthor;
 	msgsigned->isAnonymousRank = !isDiscussionPost()
 		&& this->author()->isMegagroup();
 	history()->owner().requestItemResize(this);
@@ -2643,20 +2643,10 @@ void HistoryItem::setReplyFields(
 			}
 		}
 	} else if (const auto reply = Get<HistoryMessageReply>()) {
-		reply->topicPost = isForumPost;
-		if ((reply->replyToMsgId != replyTo)
-			&& !IsServerMsgId(reply->replyToMsgId)) {
-			reply->replyToMsgId = replyTo;
-			if (!reply->updateData(this)) {
-				RequestDependentMessageItem(
-					this,
-					reply->replyToPeerId,
-					reply->replyToMsgId);
-			}
-		}
-		if ((reply->replyToMsgTop != replyToTop)
-			&& !IsServerMsgId(reply->replyToMsgTop)) {
-			reply->replyToMsgTop = replyToTop;
+		const auto increment = (reply->topMessageId() != replyToTop)
+			&& !IsServerMsgId(reply->topMessageId());
+		reply->updateFields(this, replyTo, replyToTop, isForumPost);
+		if (increment) {
 			changeReplyToTopCounter(reply, 1);
 		}
 	}
@@ -2819,14 +2809,22 @@ bool HistoryItem::unread(not_null<Data::Thread*> thread) const {
 
 MsgId HistoryItem::replyToId() const {
 	if (const auto reply = Get<HistoryMessageReply>()) {
-		return reply->replyToId();
+		return reply->messageId();
 	}
 	return 0;
 }
 
+FullMsgId HistoryItem::replyToFullId() const {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		const auto peer = reply->externalPeerId();
+		return { peer ? peer : history()->peer->id, reply->messageId() };
+	}
+	return {};
+}
+
 MsgId HistoryItem::replyToTop() const {
 	if (const auto reply = Get<HistoryMessageReply>()) {
-		return reply->replyToTop();
+		return reply->topMessageId();
 	} else if (const auto data = GetServiceDependentData()) {
 		return data->topId;
 	}
@@ -2835,8 +2833,8 @@ MsgId HistoryItem::replyToTop() const {
 
 MsgId HistoryItem::topicRootId() const {
 	if (const auto reply = Get<HistoryMessageReply>()
-		; reply && reply->topicPost) {
-		return reply->replyToTop();
+		; reply && reply->topicPost()) {
+		return reply->topMessageId();
 	} else if (const auto data = GetServiceDependentData()
 		; data && data->topicPost && data->topId) {
 		return data->topId;
@@ -2850,11 +2848,11 @@ MsgId HistoryItem::topicRootId() const {
 
 FullStoryId HistoryItem::replyToStory() const {
 	if (const auto reply = Get<HistoryMessageReply>()) {
-		if (reply->replyToStoryId) {
-			const auto peerId = reply->replyToPeerId
-				? reply->replyToPeerId
+		if (reply->storyId()) {
+			const auto peerId = reply->externalPeerId()
+				? reply->externalPeerId()
 				: _history->peer->id;
-			return { .peer = peerId, .story = reply->replyToStoryId };
+			return { .peer = peerId, .story = reply->storyId() };
 		}
 	}
 	return {};
@@ -2862,9 +2860,9 @@ FullStoryId HistoryItem::replyToStory() const {
 
 FullReplyTo HistoryItem::replyTo() const {
 	return {
-		.msgId = replyToId(),
-		.topicRootId = topicRootId(),
+		.messageId = replyToFullId(),
 		.storyId = replyToStory(),
+		.topicRootId = topicRootId(),
 	};
 }
 
@@ -3049,7 +3047,10 @@ const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
 
 void HistoryItem::createComponents(CreateConfig &&config) {
 	uint64 mask = 0;
-	if (config.replyTo || config.replyToStory) {
+	if (config.reply.messageId
+		|| config.reply.externalSenderId
+		|| !config.reply.externalSenderName.isEmpty()
+		|| config.reply.storyId) {
 		mask |= HistoryMessageReply::Bit();
 	}
 	if (config.viaBotId) {
@@ -3058,18 +3059,18 @@ void HistoryItem::createComponents(CreateConfig &&config) {
 	if (config.viewsCount >= 0 || !config.replies.isNull) {
 		mask |= HistoryMessageViews::Bit();
 	}
-	if (!config.author.isEmpty()) {
+	if (!config.postAuthor.isEmpty()) {
 		mask |= HistoryMessageSigned::Bit();
 	} else if (_history->peer->isMegagroup() // Discussion posts signatures.
 		&& config.savedFromPeer
-		&& !config.authorOriginal.isEmpty()) {
+		&& !config.originalPostAuthor.isEmpty()) {
 		const auto savedFrom = _history->owner().peerLoaded(
 			config.savedFromPeer);
 		if (savedFrom && savedFrom->isChannel()) {
 			mask |= HistoryMessageSigned::Bit();
 		}
 	} else if ((_history->peer->isSelf() || _history->peer->isRepliesChat())
-		&& !config.authorOriginal.isEmpty()) {
+		&& !config.originalPostAuthor.isEmpty()) {
 		mask |= HistoryMessageSigned::Bit();
 	}
 	if (config.editDate != TimeId(0)) {
@@ -3087,23 +3088,18 @@ void HistoryItem::createComponents(CreateConfig &&config) {
 	UpdateComponents(mask);
 
 	if (const auto reply = Get<HistoryMessageReply>()) {
-		reply->replyToPeerId = config.replyToPeer;
-		reply->replyToMsgId = config.replyTo;
-		reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
-		reply->replyToStoryId = config.replyToStory;
-		reply->storyReply = (config.replyToStory != 0);
-		reply->topicPost = config.replyIsTopicPost;
+		reply->set(config.reply);
 		if (!reply->updateData(this)) {
-			if (reply->replyToMsgId) {
+			if (const auto messageId = reply->messageId()) {
 				RequestDependentMessageItem(
 					this,
-					reply->replyToPeerId,
-					reply->replyToMsgId);
-			} else if (reply->replyToStoryId) {
+					reply->externalPeerId(),
+					reply->messageId());
+			} else if (reply->storyId()) {
 				RequestDependentMessageStory(
 					this,
-					reply->replyToPeerId,
-					reply->replyToStoryId);
+					reply->externalPeerId(),
+					reply->storyId());
 			}
 		}
 	}
@@ -3129,9 +3125,9 @@ void HistoryItem::createComponents(CreateConfig &&config) {
 		edited->date = config.editDate;
 	}
 	if (const auto msgsigned = Get<HistoryMessageSigned>()) {
-		msgsigned->author = config.author.isEmpty()
-			? config.authorOriginal
-			: config.author;
+		msgsigned->postAuthor = config.postAuthor.isEmpty()
+			? config.originalPostAuthor
+			: config.postAuthor;
 		msgsigned->isAnonymousRank = !isDiscussionPost()
 			&& author()->isMegagroup();
 	}
@@ -3167,9 +3163,9 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
 		return;
 	}
 	forwarded->originalDate = config.originalDate;
-	const auto originalSender = config.senderOriginal
-		? config.senderOriginal
-		: !config.senderNameOriginal.isEmpty()
+	const auto originalSender = config.originalSenderId
+		? config.originalSenderId
+		: !config.originalSenderName.isEmpty()
 		? PeerId()
 		: from()->id;
 	forwarded->originalSender = originalSender
@@ -3177,11 +3173,11 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
 		: nullptr;
 	if (!forwarded->originalSender) {
 		forwarded->hiddenSenderInfo = std::make_unique<HiddenSenderInfo>(
-			config.senderNameOriginal,
+			config.originalSenderName,
 			config.imported);
 	}
 	forwarded->originalId = config.originalId;
-	forwarded->originalAuthor = config.authorOriginal;
+	forwarded->originalPostAuthor = config.originalPostAuthor;
 	forwarded->psaType = config.forwardPsaType;
 	forwarded->savedFromPeer = _history->owner().peerLoaded(
 		config.savedFromPeer);
@@ -3233,7 +3229,7 @@ TextWithEntities HistoryItem::withLocalEntities(
 			: nullptr;
 		if (document) {
 			if (const auto duration = DurationForTimestampLinks(document)) {
-				const auto context = reply->replyToMsg->fullId();
+				const auto context = reply->resolvedMessage->fullId();
 				return AddTimestampLinks(
 					textWithEntities,
 					duration,
@@ -3241,7 +3237,7 @@ TextWithEntities HistoryItem::withLocalEntities(
 			}
 		} else if (webpage) {
 			if (const auto duration = DurationForTimestampLinks(webpage)) {
-				const auto context = reply->replyToMsg->fullId();
+				const auto context = reply->resolvedMessage->fullId();
 				return AddTimestampLinks(
 					textWithEntities,
 					duration,
@@ -3290,19 +3286,28 @@ void HistoryItem::createComponentsHelper(
 	auto config = CreateConfig();
 	config.viaBotId = viaBotId;
 	if (flags & MessageFlag::HasReplyInfo) {
-		config.replyTo = replyTo.msgId;
-		config.replyToStory = replyTo.storyId.story;
-		config.replyToPeer = replyTo.storyId ? replyTo.storyId.peer : 0;
-		const auto to = LookupReplyTo(_history, replyTo.msgId);
-		const auto replyToTop = LookupReplyToTop(to);
-		config.replyToTop = replyToTop ? replyToTop : replyTo.msgId;
+		config.reply.messageId = replyTo.messageId.msg;
+		config.reply.storyId = replyTo.storyId.story;
+		config.reply.externalPeerId = replyTo.storyId
+			? replyTo.storyId.peer
+			: (replyTo.messageId && replyTo.messageId.peer
+				!= history()->peer->id)
+			? replyTo.messageId.peer
+			: PeerId();
+		const auto to = LookupReplyTo(_history, replyTo.messageId);
+		const auto replyToTop = LookupReplyToTop(_history, to);
+		config.reply.topMessageId = replyToTop
+			? replyToTop
+			: (replyTo.messageId.peer == history()->peer->id)
+			? replyTo.messageId.msg
+			: MsgId();
 		const auto forum = _history->asForum();
-		config.replyIsTopicPost = LookupReplyIsTopicPost(to)
+		config.reply.topicPost = LookupReplyIsTopicPost(to)
 			|| (to && to->Has<HistoryServiceTopicInfo>())
-			|| (forum && forum->creating(config.replyToTop));
+			|| (forum && forum->creating(config.reply.topMessageId));
 	}
 	config.markup = std::move(markup);
-	if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor;
+	if (flags & MessageFlag::HasPostAuthor) config.postAuthor = postAuthor;
 	if (flags & MessageFlag::HasViews) config.viewsCount = 1;
 
 	createComponents(std::move(config));
@@ -3392,26 +3397,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
 		});
 	}
 	if (const auto reply = data.vreply_to()) {
-		reply->match([&](const MTPDmessageReplyHeader &data) {
-			// #TODO replies
-			if (const auto id = data.vreply_to_msg_id().value_or_empty()) {
-				if (const auto peer = data.vreply_to_peer_id()) {
-					config.replyToPeer = peerFromMTP(*peer);
-					if (config.replyToPeer == _history->peer->id) {
-						config.replyToPeer = 0;
-					}
-				}
-				const auto owner = &_history->owner();
-				config.replyTo = data.is_reply_to_scheduled()
-					? owner->scheduledMessages().localMessageId(id)
-					: id;
-				config.replyToTop = data.vreply_to_top_id().value_or(id);
-				config.replyIsTopicPost = data.is_forum_topic();
-			}
-		}, [&](const MTPDmessageReplyStoryHeader &data) {
-			config.replyToPeer = peerFromUser(data.vuser_id());
-			config.replyToStory = data.vstory_id().v;
-		});
+		config.reply = ReplyFieldsFromMTP(history(), *reply);
 	}
 	config.viaBotId = data.vvia_bot_id().value_or_empty();
 	config.viewsCount = data.vviews().value_or(-1);
@@ -3421,7 +3407,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
 		: HistoryMessageRepliesData(data.vreplies());
 	config.markup = HistoryMessageMarkupData(data.vreply_markup());
 	config.editDate = data.vedit_date().value_or_empty();
-	config.author = qs(data.vpost_author().value_or_empty());
+	config.postAuthor = qs(data.vpost_author().value_or_empty());
 	createComponents(std::move(config));
 }
 
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index d47660393..c04e6f9de 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -465,6 +465,7 @@ public:
 	void setText(const TextWithEntities &textWithEntities);
 
 	[[nodiscard]] MsgId replyToId() const;
+	[[nodiscard]] FullMsgId replyToFullId() const;
 	[[nodiscard]] MsgId replyToTop() const;
 	[[nodiscard]] MsgId topicRootId() const;
 	[[nodiscard]] FullStoryId replyToStory() const;
@@ -473,12 +474,12 @@ public:
 
 	[[nodiscard]] not_null<PeerData*> author() const;
 
-	[[nodiscard]] TimeId dateOriginal() const;
-	[[nodiscard]] PeerData *senderOriginal() const;
+	[[nodiscard]] TimeId originalDate() const;
+	[[nodiscard]] PeerData *originalSender() const;
 	[[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const;
 	[[nodiscard]] not_null<PeerData*> fromOriginal() const;
-	[[nodiscard]] QString authorOriginal() const;
-	[[nodiscard]] MsgId idOriginal() const;
+	[[nodiscard]] QString originalPostAuthor() const;
+	[[nodiscard]] MsgId originalId() const;
 
 	[[nodiscard]] bool isEmpty() const;
 
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index 30d032bce..7bcaeb649 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/history_item_components.h"
 
+#include "api/api_text_entities.h"
 #include "base/qt/qt_key_modifiers.h"
 #include "lang/lang_keys.h"
 #include "ui/effects/ripple_animation.h"
@@ -38,6 +39,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_web_page.h"
 #include "data/data_file_click_handler.h"
+#include "data/data_scheduled_messages.h"
+#include "data/data_session.h"
 #include "data/data_stories.h"
 #include "main/main_session.h"
 #include "window/window_session_controller.h"
@@ -64,7 +67,7 @@ void HistoryMessageVia::create(
 			lt_inline_bot,
 			'@' + bot->username()));
 	link = std::make_shared<LambdaClickHandler>([bot = this->bot](
-		ClickContext context) {
+			ClickContext context) {
 		const auto my = context.other.value<ClickHandlerContext>();
 		if (const auto controller = my.sessionWindow.get()) {
 			if (base::IsCtrlPressed()) {
@@ -183,13 +186,13 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
 			? originalSender->name()
 			: hiddenSenderInfo->name)
 	};
-	if (!originalAuthor.isEmpty()) {
+	if (!originalPostAuthor.isEmpty()) {
 		phrase = tr::lng_forwarded_signed(
 			tr::now,
 			lt_channel,
 			name,
 			lt_user,
-			{ .text = originalAuthor },
+			{ .text = originalPostAuthor },
 			Ui::Text::WithEntities);
 	} else {
 		phrase = name;
@@ -261,6 +264,51 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
 	}
 }
 
+ReplyFields ReplyFieldsFromMTP(
+		not_null<History*> history,
+		const MTPMessageReplyHeader &reply) {
+	return reply.match([&](const MTPDmessageReplyHeader &data) {
+		auto result = ReplyFields();
+		if (const auto peer = data.vreply_to_peer_id()) {
+			result.externalPeerId = peerFromMTP(*peer);
+			if (result.externalPeerId == history->peer->id) {
+				result.externalPeerId = 0;
+			}
+		}
+		const auto owner = &history->owner();
+		if (const auto id = data.vreply_to_msg_id().value_or_empty()) {
+			result.messageId = data.is_reply_to_scheduled()
+				? owner->scheduledMessages().localMessageId(id)
+				: id;
+			result.topMessageId
+				= data.vreply_to_top_id().value_or(id);
+			result.topicPost = data.is_forum_topic();
+		}
+		if (const auto header = data.vreply_header()) {
+			const auto &data = header->data();
+			result.externalPostAuthor
+				= qs(data.vpost_author().value_or_empty());
+			result.externalSenderId = data.vfrom_id()
+				? peerFromMTP(*data.vfrom_id())
+				: PeerId();
+			result.externalSenderName
+				= qs(data.vfrom_name().value_or_empty());
+		}
+		result.quote = TextWithEntities{
+			qs(data.vquote_text().value_or_empty()),
+			Api::EntitiesFromMTP(
+				&owner->session(),
+				data.vquote_entities().value_or_empty()),
+		};
+		return result;
+	}, [&](const MTPDmessageReplyStoryHeader &data) {
+		return ReplyFields{
+			.externalPeerId = peerFromUser(data.vuser_id()),
+			.storyId = data.vstory_id().v,
+		};
+	});
+}
+
 HistoryMessageReply::HistoryMessageReply() = default;
 
 HistoryMessageReply &HistoryMessageReply::operator=(
@@ -268,8 +316,8 @@ HistoryMessageReply &HistoryMessageReply::operator=(
 
 HistoryMessageReply::~HistoryMessageReply() {
 	// clearData() should be called by holder.
-	Expects(replyToMsg.empty());
-	Expects(replyToVia == nullptr);
+	Expects(resolvedMessage.empty());
+	Expects(originalVia == nullptr);
 }
 
 bool HistoryMessageReply::updateData(
@@ -277,166 +325,228 @@ bool HistoryMessageReply::updateData(
 		bool force) {
 	const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
 	if (!force) {
-		if ((replyToMsg || !replyToMsgId)
-			&& (replyToStory || !replyToStoryId)) {
+		if (resolvedMessage || resolvedStory || _deleted) {
 			return true;
 		}
 	}
-	const auto peerId = replyToPeerId
-		? replyToPeerId
+	const auto peerId = _fields.externalPeerId
+		? _fields.externalPeerId
 		: holder->history()->peer->id;
-	if (!replyToMsg && replyToMsgId) {
-		replyToMsg = holder->history()->owner().message(
+	if (!resolvedMessage && _fields.messageId) {
+		resolvedMessage = holder->history()->owner().message(
 			peerId,
-			replyToMsgId);
-		if (replyToMsg) {
-			if (replyToMsg->isEmpty()) {
+			_fields.messageId);
+		if (resolvedMessage) {
+			if (resolvedMessage->isEmpty()) {
 				// Really it is deleted.
-				replyToMsg = nullptr;
+				resolvedMessage = nullptr;
 				force = true;
 			} else {
 				holder->history()->owner().registerDependentMessage(
 					holder,
-					replyToMsg.get());
+					resolvedMessage.get());
 			}
 		}
 	}
-	if (!replyToStory && replyToStoryId) {
+	if (!resolvedStory && _fields.storyId) {
 		const auto maybe = holder->history()->owner().stories().lookup({
 			peerId,
-			replyToStoryId,
+			_fields.storyId,
 		});
 		if (maybe) {
-			replyToStory = *maybe;
+			resolvedStory = *maybe;
 			holder->history()->owner().stories().registerDependentMessage(
 				holder,
-				replyToStory.get());
+				resolvedStory.get());
 		} else if (maybe.error() == Data::NoStory::Deleted) {
 			force = true;
 		}
 	}
 
-	if (replyToMsg || replyToStory) {
+	const auto external = _fields.externalSenderId
+		|| !_fields.externalSenderName.isEmpty();
+	if (resolvedMessage || resolvedStory || (external && force)) {
 		const auto repaint = [=] { holder->customEmojiRepaint(); };
 		const auto context = Core::MarkedTextContext{
 			.session = &holder->history()->session(),
 			.customEmojiRepaint = repaint,
 		};
-		replyToText.setMarkedText(
+		const auto text = !_fields.quote.empty()
+			? _fields.quote
+			: resolvedMessage
+			? resolvedMessage->inReplyText()
+			: resolvedStory
+			? resolvedStory->inReplyText()
+			: TextWithEntities{ u"..."_q };
+		_text.setMarkedText(
 			st::defaultTextStyle,
-			(replyToMsg
-				? replyToMsg->inReplyText()
-				: replyToStory->inReplyText()),
+			text,
 			Ui::DialogTextOptions(),
 			context);
 
 		updateName(holder);
-
-		setReplyToLinkFrom(holder);
-		if (replyToMsg && !replyToMsg->Has<HistoryMessageForwarded>()) {
-			if (auto bot = replyToMsg->viaBot()) {
-				replyToVia = std::make_unique<HistoryMessageVia>();
-				replyToVia->create(
+		setLinkFrom(holder);
+		if (resolvedMessage
+			&& !resolvedMessage->Has<HistoryMessageForwarded>()) {
+			if (const auto bot = resolvedMessage->viaBot()) {
+				originalVia = std::make_unique<HistoryMessageVia>();
+				originalVia->create(
 					&holder->history()->owner(),
 					peerToUser(bot->id));
 			}
 		}
 
-		if (replyToMsg) {
-			const auto peer = replyToMsg->history()->peer;
-			replyToColorKey = (!holder->out()
+		if (resolvedMessage) {
+			const auto peer = resolvedMessage->history()->peer;
+			_colorKey = (!holder->out()
 					&& (peer->isMegagroup() || peer->isChat())
-					&& replyToMsg->from()->isUser())
-				? replyToMsg->from()->id
-				: PeerId(0);
+					&& resolvedMessage->from()->isUser())
+				? resolvedMessage->from()->id
+				: PeerId();
 		} else {
-			replyToColorKey = PeerId(0);
+			resolvedMessage = 0;
 		}
 
-		const auto media = replyToMsg ? replyToMsg->media() : nullptr;
+		const auto media = resolvedMessage
+			? resolvedMessage->media()
+			: nullptr;
 		if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
 			spoiler = nullptr;
 		} else if (!spoiler) {
 			spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
 		}
 	} else if (force) {
-		replyToMsgId = 0;
-		replyToStoryId = 0;
-		replyToColorKey = PeerId(0);
+		if (_fields.messageId || _fields.storyId) {
+			_deleted = true;
+		}
+		_colorKey = 0;
 		spoiler = nullptr;
 	}
 	if (force) {
 		holder->history()->owner().requestItemResize(holder);
 	}
-	return (replyToMsg || !replyToMsgId)
-		&& (replyToStory || !replyToStoryId);
+	return resolvedMessage
+		|| resolvedStory
+		|| (external && !_fields.messageId)
+		|| _deleted;
 }
 
-void HistoryMessageReply::setReplyToLinkFrom(
+void HistoryMessageReply::set(ReplyFields fields) {
+	_fields = std::move(fields);
+}
+
+void HistoryMessageReply::updateFields(
+		not_null<HistoryItem*> holder,
+		MsgId messageId,
+		MsgId topMessageId,
+		bool topicPost) {
+	_fields.topicPost = topicPost;
+	if ((_fields.messageId != messageId)
+		&& !IsServerMsgId(_fields.messageId)) {
+		_fields.messageId = messageId;
+		if (!updateData(holder)) {
+			RequestDependentMessageItem(
+				holder,
+				_fields.externalPeerId,
+				_fields.messageId);
+		}
+	}
+	if ((_fields.topMessageId != topMessageId)
+		&& !IsServerMsgId(_fields.topMessageId)) {
+		_fields.topMessageId = topMessageId;
+	}
+}
+
+void HistoryMessageReply::setLinkFrom(
 		not_null<HistoryItem*> holder) {
-	replyToLnk = replyToMsg
-		? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId())
-		: replyToStory
-		? JumpToStoryClickHandler(replyToStory.get())
+	const auto externalPeerId = _fields.externalSenderId;
+	const auto external = externalPeerId
+		|| !_fields.externalSenderName.isEmpty();
+	const auto externalLink = [=](ClickContext context) {
+		const auto my = context.other.value<ClickHandlerContext>();
+		if (const auto controller = my.sessionWindow.get()) {
+			if (externalPeerId) {
+				controller->showPeerInfo(
+					controller->session().data().peer(externalPeerId));
+			} else {
+				controller->showToast(u"External reply"_q);
+			}
+		}
+	};
+	_link = resolvedMessage
+		? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId())
+		: resolvedStory
+		? JumpToStoryClickHandler(resolvedStory.get())
+		: (external && !_fields.messageId)
+		? std::make_shared<LambdaClickHandler>(externalLink)
 		: nullptr;
 }
 
+void HistoryMessageReply::setTopMessageId(MsgId topMessageId) {
+	_fields.topMessageId = topMessageId;
+}
+
 void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
-	replyToVia = nullptr;
-	if (replyToMsg) {
+	originalVia = nullptr;
+	if (resolvedMessage) {
 		holder->history()->owner().unregisterDependentMessage(
 			holder,
-			replyToMsg.get());
-		replyToMsg = nullptr;
+			resolvedMessage.get());
+		resolvedMessage = nullptr;
 	}
-	if (replyToStory) {
+	if (resolvedStory) {
 		holder->history()->owner().stories().unregisterDependentMessage(
 			holder,
-			replyToStory.get());
-		replyToStory = nullptr;
+			resolvedStory.get());
+		resolvedStory = nullptr;
 	}
-	replyToMsgId = 0;
-	replyToStoryId = 0;
+	_deleted = true;
 	refreshReplyToMedia();
 }
 
-PeerData *HistoryMessageReply::replyToFrom(
-		not_null<HistoryItem*> holder) const {
-	if (!replyToMsg) {
-		return nullptr;
+PeerData *HistoryMessageReply::sender(not_null<HistoryItem*> holder) const {
+	if (resolvedStory) {
+		return resolvedStory->peer();
+	} else if (!resolvedMessage) {
+		if (!_externalSender && _fields.externalSenderId) {
+			_externalSender = holder->history()->owner().peer(
+				_fields.externalSenderId);
+		}
+		return _externalSender;
 	} else if (holder->Has<HistoryMessageForwarded>()) {
-		if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
-			return fwd->originalSender;
+		// Forward of a reply. Show reply-to original sender.
+		const auto forwarded
+			= resolvedMessage->Get<HistoryMessageForwarded>();
+		if (forwarded) {
+			return forwarded->originalSender;
 		}
 	}
-	if (const auto from = replyToMsg->displayFrom()) {
+	if (const auto from = resolvedMessage->displayFrom()) {
 		return from;
 	}
-	return replyToMsg->author().get();
+	return resolvedMessage->author().get();
 }
 
-QString HistoryMessageReply::replyToFromName(
+QString HistoryMessageReply::senderName(
 		not_null<HistoryItem*> holder) const {
-	if (replyToStory) {
-		return replyToFromName(replyToStory->peer());
-	} else if (!replyToMsg) {
-		return QString();
+	if (const auto peer = sender(holder)) {
+		return senderName(peer);
+	} else if (!resolvedMessage) {
+		return _fields.externalSenderName;
 	} else if (holder->Has<HistoryMessageForwarded>()) {
-		if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
-			return fwd->originalSender
-				? replyToFromName(fwd->originalSender)
-				: fwd->hiddenSenderInfo->name;
+		// Forward of a reply. Show reply-to original sender.
+		const auto forwarded
+			= resolvedMessage->Get<HistoryMessageForwarded>();
+		if (forwarded) {
+			Assert(forwarded->hiddenSenderInfo != nullptr);
+			return forwarded->hiddenSenderInfo->name;
 		}
 	}
-	if (const auto from = replyToMsg->displayFrom()) {
-		return replyToFromName(from);
-	}
-	return replyToFromName(replyToMsg->author());
+	return QString();
 }
 
-QString HistoryMessageReply::replyToFromName(
-		not_null<PeerData*> peer) const {
-	if (const auto user = replyToVia ? peer->asUser() : nullptr) {
+QString HistoryMessageReply::senderName(not_null<PeerData*> peer) const {
+	if (const auto user = originalVia ? peer->asUser() : nullptr) {
 		return user->firstName;
 	}
 	return peer->name();
@@ -444,9 +554,9 @@ QString HistoryMessageReply::replyToFromName(
 
 bool HistoryMessageReply::isNameUpdated(
 		not_null<HistoryItem*> holder) const {
-	if (const auto from = replyToFrom(holder)) {
-		if (replyToVersion < from->nameVersion()) {
-			updateName(holder);
+	if (const auto from = sender(holder)) {
+		if (_nameVersion < from->nameVersion()) {
+			updateName(holder, from);
 			return true;
 		}
 	}
@@ -454,55 +564,73 @@ bool HistoryMessageReply::isNameUpdated(
 }
 
 void HistoryMessageReply::updateName(
-		not_null<HistoryItem*> holder) const {
-	if (const auto name = replyToFromName(holder); !name.isEmpty()) {
-		replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
-		if (const auto from = replyToFrom(holder)) {
-			replyToVersion = from->nameVersion();
-		} else if (replyToMsg) {
-			replyToVersion = replyToMsg->author()->nameVersion();
-		} else {
-			replyToVersion = replyToStory->peer()->nameVersion();
+		not_null<HistoryItem*> holder,
+		std::optional<PeerData*> resolvedSender) const {
+	const auto peer = resolvedSender.value_or(sender(holder));
+	const auto name = peer ? senderName(peer) : senderName(holder);
+	if (!name.isEmpty()) {
+		_name.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
+		if (peer) {
+			_nameVersion = peer->nameVersion();
 		}
-		bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
-			|| (replyToMsg
-				&& replyToMsg->media()
-				&& replyToMsg->media()->hasReplyPreview());
-		int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
-		int32 w = replyToName.maxWidth();
-		if (replyToVia) {
-			w += st::msgServiceFont->spacew + replyToVia->maxWidth;
+		bool hasPreview = (resolvedStory
+			&& resolvedStory->hasReplyPreview())
+			|| (resolvedMessage
+				&& resolvedMessage->media()
+				&& resolvedMessage->media()->hasReplyPreview());
+		int32 previewSkip = hasPreview
+			? (st::msgReplyBarSize.height()
+				+ st::msgReplyBarSkip
+				- st::msgReplyBarSize.width()
+				- st::msgReplyBarPos.x())
+			: 0;
+		int32 w = _name.maxWidth();
+		if (originalVia) {
+			w += st::msgServiceFont->spacew + originalVia->maxWidth;
 		}
 
-		maxReplyWidth = previewSkip
+		_maxWidth = previewSkip
 			+ std::max(
 				w,
-				std::min(replyToText.maxWidth(), st::maxSignatureSize))
-			+ (storyReply
+				std::min(_text.maxWidth(), st::maxSignatureSize))
+			+ (_fields.storyId
 				? (st::dialogsMiniReplyStory.skipText
 					+ st::dialogsMiniReplyStory.icon.icon.width())
 				: 0);
 	} else {
-		maxReplyWidth = st::msgDateFont->width(statePhrase());
+		_maxWidth = st::msgDateFont->width(statePhrase());
 	}
-	maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right();
+	_maxWidth = st::msgReplyPadding.left()
+		+ st::msgReplyBarSkip
+		+ _maxWidth
+		+ st::msgReplyPadding.right();
 }
 
 void HistoryMessageReply::resize(int width) const {
-	if (replyToVia) {
-		bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
-			|| (replyToMsg
-				&& replyToMsg->media()
-				&& replyToMsg->media()->hasReplyPreview());
-		int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
-		replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
+	if (originalVia) {
+		bool hasPreview = (resolvedStory
+			&& resolvedStory->hasReplyPreview())
+			|| (resolvedMessage
+				&& resolvedMessage->media()
+				&& resolvedMessage->media()->hasReplyPreview());
+		int previewSkip = hasPreview
+			? (st::msgReplyBarSize.height()
+				+ st::msgReplyBarSkip
+				- st::msgReplyBarSize.width()
+				- st::msgReplyBarPos.x())
+			: 0;
+		originalVia->resize(width
+			- st::msgReplyBarSkip
+			- previewSkip
+			- _name.maxWidth()
+			- st::msgServiceFont->spacew);
 	}
 }
 
 void HistoryMessageReply::itemRemoved(
 		not_null<HistoryItem*> holder,
 		not_null<HistoryItem*> removed) {
-	if (replyToMsg.get() == removed) {
+	if (resolvedMessage.get() == removed) {
 		clearData(holder);
 		holder->history()->owner().requestItemResize(holder);
 	}
@@ -511,7 +639,7 @@ void HistoryMessageReply::itemRemoved(
 void HistoryMessageReply::storyRemoved(
 		not_null<HistoryItem*> holder,
 		not_null<Data::Story*> removed) {
-	if (replyToStory.get() == removed) {
+	if (resolvedStory.get() == removed) {
 		clearData(holder);
 		holder->history()->owner().requestItemResize(holder);
 	}
@@ -533,8 +661,8 @@ void HistoryMessageReply::paint(
 		const auto outerWidth = w + 2 * x;
 		const auto &bar = !inBubble
 			? st->msgImgReplyBarColor()
-			: replyToColorKey
-			? HistoryView::FromNameFg(context, replyToColorKey)
+			: _colorKey
+			? HistoryView::FromNameFg(context, _colorKey)
 			: stm->msgReplyBarColor;
 		const auto rbar = style::rtlrect(
 			x + st::msgReplyBarPos.x(),
@@ -565,9 +693,10 @@ void HistoryMessageReply::paint(
 	const auto pausedSpoiler = context.paused
 		|| On(PowerSaving::kChatSpoiler);
 	if (w > st::msgReplyBarSkip) {
-		if (replyToMsg || replyToStory) {
-			const auto media = replyToMsg ? replyToMsg->media() : nullptr;
-			auto hasPreview = (replyToStory && replyToStory->hasReplyPreview()) || (media && media->hasReplyPreview());
+		if (resolvedMessage || resolvedStory) {
+			const auto media = resolvedMessage ? resolvedMessage->media() : nullptr;
+			auto hasPreview = (media && media->hasReplyPreview())
+				|| (resolvedStory && resolvedStory->hasReplyPreview());
 			if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
 				hasPreview = false;
 			}
@@ -576,7 +705,7 @@ void HistoryMessageReply::paint(
 			if (hasPreview) {
 				const auto image = media
 					? media->replyPreview()
-					: replyToStory->replyPreview();
+					: resolvedStory->replyPreview();
 				if (image) {
 					auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
 					const auto preview = image->pixSingle(
@@ -604,26 +733,26 @@ void HistoryMessageReply::paint(
 			if (w > st::msgReplyBarSkip + previewSkip) {
 				p.setPen(!inBubble
 					? st->msgImgReplyBarColor()
-					: replyToColorKey
-					? HistoryView::FromNameFg(context, replyToColorKey)
+					: _colorKey
+					? HistoryView::FromNameFg(context, _colorKey)
 					: stm->msgServiceFg);
-				replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
-				if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) {
+				_name.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
+				if (originalVia && w > st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew) {
 					p.setFont(st::msgServiceFont);
-					p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text);
+					p.drawText(x + st::msgReplyBarSkip + previewSkip + _name.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text);
 				}
 
 				p.setPen(inBubble
 					? stm->historyTextFg
 					: st->msgImgReplyBarColor());
-				holder->prepareCustomEmojiPaint(p, context, replyToText);
+				holder->prepareCustomEmojiPaint(p, context, _text);
 				auto replyToTextPosition = QPoint(
 					x + st::msgReplyBarSkip + previewSkip,
 					y + st::msgReplyPadding.top() + st::msgServiceNameFont->height);
 				const auto replyToTextPalette = &(inBubble
 					? stm->replyTextPalette
 					: st->imgReplyTextPalette());
-				if (storyReply) {
+				if (_fields.storyId) {
 					st::dialogsMiniReplyStory.icon.icon.paint(
 						p,
 						replyToTextPosition,
@@ -634,7 +763,7 @@ void HistoryMessageReply::paint(
 							+ st::dialogsMiniReplyStory.icon.icon.width(),
 						0);
 				}
-				replyToText.draw(p, {
+				_text.draw(p, {
 					.position = replyToTextPosition,
 					.availableWidth = w - st::msgReplyBarSkip - previewSkip,
 					.palette = replyToTextPalette,
@@ -657,10 +786,14 @@ void HistoryMessageReply::paint(
 	}
 }
 
+void HistoryMessageReply::unloadPersistentAnimation() {
+	_text.unloadPersistentAnimation();
+}
+
 QString HistoryMessageReply::statePhrase() const {
-	return (replyToMsgId || replyToStoryId)
+	return ((_fields.messageId || _fields.storyId) && !_deleted)
 		? tr::lng_profile_loading(tr::now)
-		: storyReply
+		: _fields.storyId
 		? tr::lng_deleted_story(tr::now)
 		: tr::lng_deleted_message(tr::now);
 }
@@ -668,7 +801,7 @@ QString HistoryMessageReply::statePhrase() const {
 void HistoryMessageReply::refreshReplyToMedia() {
 	replyToDocumentId = 0;
 	replyToWebPageId = 0;
-	if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) {
+	if (const auto media = resolvedMessage ? resolvedMessage->media() : nullptr) {
 		if (const auto document = media->document()) {
 			replyToDocumentId = document->id;
 		} else if (const auto webpage = media->webpage()) {
diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h
index 6416475ab..4cd033879 100644
--- a/Telegram/SourceFiles/history/history_item_components.h
+++ b/Telegram/SourceFiles/history/history_item_components.h
@@ -75,7 +75,7 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
 };
 
 struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {
-	QString author;
+	QString postAuthor;
 	bool isAnonymousRank = false;
 };
 
@@ -123,7 +123,7 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
 	TimeId originalDate = 0;
 	PeerData *originalSender = nullptr;
 	std::unique_ptr<HiddenSenderInfo> hiddenSenderInfo;
-	QString originalAuthor;
+	QString originalPostAuthor;
 	QString psaType;
 	MsgId originalId = 0;
 	mutable Ui::Text::String text = { 1 };
@@ -226,6 +226,22 @@ private:
 
 };
 
+struct ReplyFields {
+	TextWithEntities quote;
+	PeerId externalSenderId = 0;
+	QString externalSenderName;
+	QString externalPostAuthor;
+	PeerId externalPeerId = 0;
+	MsgId messageId = 0;
+	MsgId topMessageId = 0;
+	StoryId storyId = 0;
+	bool topicPost = false;
+};
+
+[[nodiscard]] ReplyFields ReplyFieldsFromMTP(
+	not_null<History*> history,
+	const MTPMessageReplyHeader &reply);
+
 struct HistoryMessageReply
 	: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
 	HistoryMessageReply();
@@ -238,17 +254,25 @@ struct HistoryMessageReply
 
 	static constexpr auto kBarAlpha = 230. / 255.;
 
+	void set(ReplyFields fields);
+
+	void updateFields(
+		not_null<HistoryItem*> holder,
+		MsgId messageId,
+		MsgId topMessageId,
+		bool topicPost);
 	bool updateData(not_null<HistoryItem*> holder, bool force = false);
 
 	// Must be called before destructor.
 	void clearData(not_null<HistoryItem*> holder);
 
-	[[nodiscard]] PeerData *replyToFrom(not_null<HistoryItem*> holder) const;
-	[[nodiscard]] QString replyToFromName(
-		not_null<HistoryItem*> holder) const;
-	[[nodiscard]] QString replyToFromName(not_null<PeerData*> peer) const;
+	[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
+	[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
+	[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
 	[[nodiscard]] bool isNameUpdated(not_null<HistoryItem*> holder) const;
-	void updateName(not_null<HistoryItem*> holder) const;
+	void updateName(
+		not_null<HistoryItem*> holder,
+		std::optional<PeerData*> resolvedSender = std::nullopt) const;
 	void resize(int width) const;
 	void itemRemoved(
 		not_null<HistoryItem*> holder,
@@ -265,52 +289,63 @@ struct HistoryMessageReply
 		int y,
 		int w,
 		bool inBubble) const;
+	void unloadPersistentAnimation();
 
-	[[nodiscard]] PeerId replyToPeer() const {
-		return replyToPeerId;
+	[[nodiscard]] PeerId colorKey() const {
+		return _colorKey;
 	}
-	[[nodiscard]] MsgId replyToId() const {
-		return replyToMsgId;
+	[[nodiscard]] PeerId externalPeerId() const {
+		return _fields.externalPeerId;
 	}
-	[[nodiscard]] MsgId replyToTop() const {
-		return replyToMsgTop;
+	[[nodiscard]] MsgId messageId() const {
+		return _fields.messageId;
 	}
-	[[nodiscard]] int replyToWidth() const {
-		return maxReplyWidth;
+	[[nodiscard]] StoryId storyId() const {
+		return _fields.storyId;
 	}
-	[[nodiscard]] ClickHandlerPtr replyToLink() const {
-		return replyToLnk;
+	[[nodiscard]] MsgId topMessageId() const {
+		return _fields.topMessageId;
+	}
+	[[nodiscard]] int maxWidth() const {
+		return _maxWidth;
+	}
+	[[nodiscard]] ClickHandlerPtr link() const {
+		return _link;
+	}
+	[[nodiscard]] bool topicPost() const {
+		return _fields.topicPost;
 	}
 	[[nodiscard]] QString statePhrase() const;
-	void setReplyToLinkFrom(not_null<HistoryItem*> holder);
+
+	void setLinkFrom(not_null<HistoryItem*> holder);
+	void setTopMessageId(MsgId topMessageId);
 
 	void refreshReplyToMedia();
 
-	PeerId replyToPeerId = 0;
-	MsgId replyToMsgId = 0;
-	MsgId replyToMsgTop = 0;
-	StoryId replyToStoryId = 0;
-	using ColorKey = PeerId;
-	ColorKey replyToColorKey = 0;
 	DocumentId replyToDocumentId = 0;
 	WebPageId replyToWebPageId = 0;
-	ReplyToMessagePointer replyToMsg;
-	ReplyToStoryPointer replyToStory;
-	std::unique_ptr<HistoryMessageVia> replyToVia;
+	ReplyToMessagePointer resolvedMessage;
+	ReplyToStoryPointer resolvedStory;
+	std::unique_ptr<HistoryMessageVia> originalVia;
 	std::unique_ptr<Ui::SpoilerAnimation> spoiler;
-	ClickHandlerPtr replyToLnk;
-	mutable Ui::Text::String replyToName, replyToText;
-	mutable int replyToVersion = 0;
-	mutable int maxReplyWidth = 0;
 	int toWidth = 0;
-	bool topicPost = false;
-	bool storyReply = false;
 
-	struct final {
+	struct {
 		mutable std::unique_ptr<Ui::RippleAnimation> animation;
 		QPoint lastPoint;
 	} ripple;
 
+private:
+	ReplyFields _fields;
+	PeerId _colorKey = 0;
+	ClickHandlerPtr _link;
+	mutable Ui::Text::String _name;
+	mutable Ui::Text::String _text;
+	mutable PeerData *_externalSender = nullptr;
+	mutable int _maxWidth = 0;
+	mutable int _nameVersion = 0;
+	bool _deleted = false;
+
 };
 
 struct HistoryMessageTranslation
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
index 4129df0ad..be7838083 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.cpp
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -192,13 +192,14 @@ bool ShouldSendSilent(
 			&& peer->session().settings().supportAllSilent());
 }
 
-HistoryItem *LookupReplyTo(not_null<History*> history, MsgId replyToId) {
-	const auto &owner = history->owner();
-	return owner.message(history->peer, replyToId);
+HistoryItem *LookupReplyTo(not_null<History*> history, FullMsgId replyTo) {
+	return history->owner().message(replyTo);
 }
 
-MsgId LookupReplyToTop(HistoryItem *replyTo) {
-	return replyTo ? replyTo->replyToTop() : 0;
+MsgId LookupReplyToTop(not_null<History*> history, HistoryItem *replyTo) {
+	return (replyTo && replyTo->history() == history)
+		? replyTo->replyToTop()
+		: 0;
 }
 
 bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
@@ -360,27 +361,21 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
 				MTP_long(peerToUser(replyTo.storyId.peer).bare),
 				MTP_int(replyTo.storyId.story));
 		}
-		const auto to = LookupReplyTo(action.history, replyTo.msgId);
-		if (const auto replyToTop = LookupReplyToTop(to)) {
-			using Flag = MTPDmessageReplyHeader::Flag;
-			return MTP_messageReplyHeader(
-				MTP_flags(Flag::f_reply_to_top_id
-					| (LookupReplyIsTopicPost(to)
-						? Flag::f_forum_topic
-						: Flag(0))),
-				MTP_int(replyTo.msgId),
-				MTPPeer(),
-				MTPMessageFwdHeader(), // reply_header
-				MTP_int(replyToTop),
-				MTPstring(), // quote_text
-				MTPVector<MTPMessageEntity>()); // quote_entities
-		}
+		using Flag = MTPDmessageReplyHeader::Flag;
+		const auto historyPeer = action.history->peer->id;
+		const auto externalPeerId = (replyTo.messageId.peer == historyPeer)
+			? PeerId()
+			: replyTo.messageId.peer;
+		const auto to = LookupReplyTo(action.history, replyTo.messageId);
+		const auto replyToTop = LookupReplyToTop(action.history, to);
 		return MTP_messageReplyHeader(
-			MTP_flags(0),
-			MTP_int(replyTo.msgId),
-			MTPPeer(), // reply_to_peer_id
+			MTP_flags(Flag::f_reply_to_msg_id
+				| (replyToTop ? Flag::f_reply_to_top_id : Flag())
+				| (externalPeerId ? Flag::f_reply_to_peer_id : Flag())),
+			MTP_int(replyTo.messageId.msg),
+			peerToMTP(externalPeerId),
 			MTPMessageFwdHeader(), // reply_header
-			MTPint(), // reply_to_top_id
+			MTP_int(replyToTop), // reply_to_top_id
 			MTPstring(), // quote_text
 			MTPVector<MTPMessageEntity>()); // quote_entities
 	}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
index e7ed74290..8dd151274 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.h
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -85,8 +85,10 @@ void RequestDependentMessageStory(
 	const Api::SendOptions &options);
 [[nodiscard]] HistoryItem *LookupReplyTo(
 	not_null<History*> history,
-	MsgId replyToId);
-[[nodiscard]] MsgId LookupReplyToTop(HistoryItem *replyTo);
+	FullMsgId replyToId);
+[[nodiscard]] MsgId LookupReplyToTop(
+	not_null<History*> history,
+	HistoryItem *replyTo);
 [[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
 
 struct SendingErrorRequest {
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 9ce5a8af3..955263ef2 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -517,7 +517,7 @@ HistoryWidget::HistoryWidget(
 			if (!_peer || isRecording()) {
 				return false;
 			}
-			const auto replyTo = (_replyToId && !_editMsgId)
+			const auto replyTo = (_replyTo && !_editMsgId)
 				? _replyEditMsg
 				: 0;
 			const auto topic = replyTo ? replyTo->topic() : nullptr;
@@ -848,9 +848,8 @@ HistoryWidget::HistoryWidget(
 	) | rpl::filter([=](const Api::SendAction &action) {
 		return (action.history == _history);
 	}) | rpl::start_with_next([=](const Api::SendAction &action) {
-		const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
-			action.history->peer->id,
-			action.replyTo.msgId));
+		const auto lastKeyboardUsed = lastForceReplyReplied(
+			action.replyTo.messageId);
 		if (action.replaceMediaOf) {
 		} else if (action.options.scheduled) {
 			cancelReply(lastKeyboardUsed);
@@ -909,7 +908,7 @@ Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const {
 	return Dialogs::EntryState{
 		.key = _history,
 		.section = Dialogs::EntryState::Section::History,
-		.currentReplyToId = replyToId(),
+		.currentReplyTo = replyTo(),
 	};
 }
 
@@ -1354,7 +1353,7 @@ void HistoryWidget::insertHashtagOrBotCommand(
 
 	// Send bot command at once, if it was not inserted by pressing Tab.
 	if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
-		sendBotCommand({ _peer, str, FullMsgId(), replyToId() });
+		sendBotCommand({ _peer, str, FullMsgId(), replyTo() });
 		session().api().finishForwarding(prepareSendAction({}));
 		setFieldText(_field->getTextWithTagsPart(_field->textCursor().position()));
 	} else {
@@ -1663,21 +1662,22 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
 	if (_editMsgId) {
 		_history->setLocalEditDraft(std::make_unique<Data::Draft>(
 			_field,
-			_editMsgId,
-			topicRootId,
+			FullReplyTo{
+				.messageId = FullMsgId(_history->peer->id, _editMsgId),
+				.topicRootId = topicRootId,
+			},
 			_previewState,
 			_saveEditMsgRequestId));
 	} else {
-		if (_replyToId || !_field->empty()) {
+		if (_replyTo || !_field->empty()) {
 			_history->setLocalDraft(std::make_unique<Data::Draft>(
 				_field,
-				_replyToId,
-				topicRootId,
+				_replyTo,
 				_previewState));
 		} else {
-			_history->clearLocalDraft({});
+			_history->clearLocalDraft(topicRootId);
 		}
-		_history->clearLocalEditDraft({});
+		_history->clearLocalEditDraft(topicRootId);
 	}
 }
 
@@ -1779,8 +1779,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(
 			};
 			_history->setLocalDraft(std::make_unique<Data::Draft>(
 				textWithTags,
-				0, // replyTo
-				0, // topicRootId
+				FullReplyTo(),
 				cursor,
 				Data::PreviewState::Allowed));
 			applyDraft();
@@ -1885,7 +1884,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
 		? _history->localDraft({})
 		: nullptr;
 	auto fieldAvailable = canWriteMessage();
-	const auto editMsgId = editDraft ? editDraft->msgId : 0;
+	const auto editMsgId = editDraft ? editDraft->reply.messageId.msg : 0;
 	if (_voiceRecordBar->isActive() || (!_canSendTexts && !editMsgId)) {
 		if (!_canSendTexts) {
 			clearFieldText(0, fieldHistoryAction);
@@ -1898,7 +1897,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
 		clearFieldText(0, fieldHistoryAction);
 		setInnerFocus();
 		_processingReplyItem = _replyEditMsg = nullptr;
-		_processingReplyId = _replyToId = 0;
+		_processingReplyTo = _replyTo = FullReplyTo();
 		setEditMsgId(0);
 		if (fieldWillBeHiddenAfterEdit) {
 			updateControlsVisibility();
@@ -1916,7 +1915,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
 		| TextUpdateEvent::SendTyping;
 
 	_processingReplyItem = _replyEditMsg = nullptr;
-	_processingReplyId = _replyToId = 0;
+	_processingReplyTo = _replyTo = FullReplyTo();
 	setEditMsgId(editMsgId);
 	updateCmdStartShown();
 	updateControlsVisibility();
@@ -1929,7 +1928,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
 		}
 	} else if (!readyToForward()) {
 		const auto draft = _history->localDraft({});
-		_processingReplyId = draft ? draft->msgId : MsgId();
+		_processingReplyTo = draft ? draft->reply : FullReplyTo();
 		processReply();
 	}
 
@@ -2147,7 +2146,8 @@ void HistoryWidget::showHistory(
 
 	_saveEditMsgRequestId = 0;
 	_processingReplyItem = _replyEditMsg = nullptr;
-	_processingReplyId = _editMsgId = _replyToId = 0;
+	_processingReplyTo = _replyTo = FullReplyTo();
+	_editMsgId = MsgId();
 	_canReplaceMedia = false;
 	_photoEditMedia = nullptr;
 	updateReplaceMediaButton();
@@ -2461,10 +2461,13 @@ void HistoryWidget::registerDraftSource() {
 	if (!_history) {
 		return;
 	}
+	const auto peerId = _history->peer->id;
 	const auto editMsgId = _editMsgId;
 	const auto draft = [=] {
 		return Storage::MessageDraft{
-			editMsgId ? editMsgId : _replyToId,
+			(editMsgId
+				? FullReplyTo{ FullMsgId(peerId, editMsgId) }
+				: _replyTo),
 			_field->getTextWithTags(),
 			_previewState,
 		};
@@ -2901,7 +2904,11 @@ void HistoryWidget::updateControlsVisibility() {
 		}
 		updateFieldPlaceholder();
 
-		if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) {
+		if (_editMsgId
+			|| _replyTo
+			|| readyToForward()
+			|| (_previewData && _previewData->pendingTill >= 0)
+			|| _kbReplyTo) {
 			if (_fieldBarCancel->isHidden()) {
 				_fieldBarCancel->show();
 				updateControlsGeometry();
@@ -2938,7 +2945,7 @@ void HistoryWidget::updateControlsVisibility() {
 			_botMenuButton->hide();
 		}
 		_kbScroll->hide();
-		if (_replyToId || readyToForward() || _kbReplyTo) {
+		if (_replyTo || readyToForward() || _kbReplyTo) {
 			if (_fieldBarCancel->isHidden()) {
 				_fieldBarCancel->show();
 				updateControlsGeometry();
@@ -3889,7 +3896,7 @@ void HistoryWidget::hideSelectorControlsAnimated() {
 Api::SendAction HistoryWidget::prepareSendAction(
 		Api::SendOptions options) const {
 	auto result = Api::SendAction(_history, options);
-	result.replyTo = { .msgId = replyToId() };
+	result.replyTo = replyTo();
 	result.options.sendAs = _sendAs
 		? _history->session().sendAsPeers().resolveChosen(
 			_history->peer).get()
@@ -4337,7 +4344,7 @@ void HistoryWidget::updateOverStates(QPoint pos) {
 		_field->y() - st::historySendPadding - st::historyReplyHeight,
 		width() - skip - _fieldBarCancel->width(),
 		st::historyReplyHeight);
-	auto inReplyEditForward = (_editMsgId || replyToId() || isReadyToForward)
+	auto inReplyEditForward = (_editMsgId || replyTo() || isReadyToForward)
 		&& replyEditForwardInfoRect.contains(pos);
 	auto inPhotoEdit = inReplyEditForward
 		&& _photoEditMedia
@@ -4387,9 +4394,9 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) {
 		return;
 	}
 
-	const auto lastKeyboardUsed = (_keyboard->forMsgId()
-			== FullMsgId(_peer->id, _history->lastKeyboardId))
-		&& (_keyboard->forMsgId() == FullMsgId(_peer->id, request.replyTo));
+	const auto forMsgId = _keyboard->forMsgId();
+	const auto lastKeyboardUsed = (forMsgId == request.replyTo.messageId)
+		&& (forMsgId == FullMsgId(_peer->id, _history->lastKeyboardId));
 
 	// 'bot' may be nullptr in case of sending from FieldAutocomplete.
 	const auto toSend = (request.replyTo/* || !bot*/)
@@ -4398,14 +4405,14 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) {
 
 	auto message = Api::MessageToSend(prepareSendAction({}));
 	message.textWithTags = { toSend, TextWithTags::Tags() };
-	message.action.replyTo.msgId = request.replyTo
+	message.action.replyTo = request.replyTo
 		? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/)
 			? request.replyTo
-			: replyToId())
-		: 0;
+			: replyTo())
+		: FullReplyTo();
 	session().api().sendMessage(std::move(message));
 	if (request.replyTo) {
-		if (_replyToId == request.replyTo) {
+		if (_replyTo == request.replyTo) {
 			cancelReply();
 			saveCloudDraft();
 		}
@@ -4418,18 +4425,25 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) {
 	setInnerFocus();
 }
 
-void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
-	if (!_peer || _peer != peer) return;
+void HistoryWidget::hideSingleUseKeyboard(FullMsgId replyToId) {
+	if (!_peer || _peer->id != replyToId.peer) {
+		return;
+	}
 
-	bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId))
-		&& (_keyboard->forMsgId() == FullMsgId(_peer->id, replyTo));
-	if (replyTo) {
-		if (_replyToId == replyTo) {
+	bool lastKeyboardUsed = (_keyboard->forMsgId() == replyToId)
+		&& (_keyboard->forMsgId()
+			== FullMsgId(_peer->id, _history->lastKeyboardId));
+	if (replyToId) {
+		if (_replyTo.messageId == replyToId) {
 			cancelReply();
 			saveCloudDraft();
 		}
-		if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
-			if (_kbShown) toggleKeyboard(false);
+		if (_keyboard->singleUse()
+			&& _keyboard->hasMarkup()
+			&& lastKeyboardUsed) {
+			if (_kbShown) {
+				toggleKeyboard(false);
+			}
 			_history->lastKeyboardUsed = true;
 		}
 	}
@@ -4772,7 +4786,7 @@ void HistoryWidget::toggleKeyboard(bool manual) {
 			_field->setMaxHeight(computeMaxFieldHeight());
 
 			_kbReplyTo = nullptr;
-			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) {
+			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyTo) {
 				_fieldBarCancel->hide();
 				updateMouseTracking();
 			}
@@ -4797,7 +4811,7 @@ void HistoryWidget::toggleKeyboard(bool manual) {
 		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
 			? session().data().message(_keyboard->forMsgId())
 			: nullptr;
-		if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
+		if (_kbReplyTo && !_editMsgId && !_replyTo && fieldEnabled) {
 			updateReplyToName();
 			updateReplyEditText(_kbReplyTo);
 		}
@@ -4817,7 +4831,7 @@ void HistoryWidget::toggleKeyboard(bool manual) {
 		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
 			? session().data().message(_keyboard->forMsgId())
 			: nullptr;
-		if (_kbReplyTo && !_editMsgId && !_replyToId) {
+		if (_kbReplyTo && !_editMsgId && !_replyTo) {
 			updateReplyToName();
 			updateReplyEditText(_kbReplyTo);
 		}
@@ -5589,11 +5603,11 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
 	if (item == _replyEditMsg && _editMsgId) {
 		cancelEdit();
 	}
-	if (item == _replyEditMsg && _replyToId) {
+	if (item == _replyEditMsg && _replyTo) {
 		cancelReply();
 	}
 	if (item == _processingReplyItem) {
-		_processingReplyId = 0;
+		_processingReplyTo = {};
 		_processingReplyItem = nullptr;
 	}
 	if (_kbReplyTo && item == _kbReplyTo) {
@@ -5617,8 +5631,12 @@ void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
 	}
 }
 
-MsgId HistoryWidget::replyToId() const {
-	return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
+FullReplyTo HistoryWidget::replyTo() const {
+	return _replyTo
+		? _replyTo
+		: _kbReplyTo
+		? FullReplyTo{ _kbReplyTo->fullId() }
+		: FullReplyTo();
 }
 
 bool HistoryWidget::hasSavedScroll() const {
@@ -5753,7 +5771,7 @@ void HistoryWidget::updateHistoryGeometry(
 		} else if (writeRestriction().has_value()) {
 			newScrollHeight -= _unblock->height();
 		}
-		if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) {
+		if (_editMsgId || replyTo() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) {
 			newScrollHeight -= st::historyReplyHeight;
 		}
 		if (_kbShown) {
@@ -5991,9 +6009,9 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 	const auto wasVisible = _kbShown || _kbReplyTo;
 	const auto wasMsgId = _keyboard->forMsgId();
 	auto changed = false;
-	if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) {
+	if ((_replyTo && !_replyEditMsg) || _editMsgId || !_history) {
 		changed = _keyboard->updateMarkup(nullptr, force);
-	} else if (_replyToId && _replyEditMsg) {
+	} else if (_replyTo && _replyEditMsg) {
 		changed = _keyboard->updateMarkup(_replyEditMsg, force);
 	} else {
 		const auto keyboardItem = _history->lastKeyboardId
@@ -6010,7 +6028,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 		_kbScroll->scrollTo({ 0, 0 });
 	}
 
-	bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg);
+	bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyTo || !_replyEditMsg);
 	if (hasMarkup || forceReply) {
 		if (_keyboard->singleUse()
 			&& _keyboard->hasMarkup()
@@ -6019,7 +6037,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 			&& _history->lastKeyboardUsed) {
 			_history->lastKeyboardHiddenId = _history->lastKeyboardId;
 		}
-		if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) {
+		if (!isSearching() && !isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) {
 			if (!_showAnimation) {
 				if (hasMarkup) {
 					_kbScroll->show();
@@ -6040,7 +6058,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 			_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
 				? session().data().message(_keyboard->forMsgId())
 				: nullptr;
-			if (_kbReplyTo && !_replyToId) {
+			if (_kbReplyTo && !_replyTo) {
 				updateReplyToName();
 				updateReplyEditText(_kbReplyTo);
 			}
@@ -6055,7 +6073,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 			_field->setMaxHeight(computeMaxFieldHeight());
 			_kbShown = false;
 			_kbReplyTo = nullptr;
-			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) {
+			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo) {
 				_fieldBarCancel->hide();
 				updateMouseTracking();
 			}
@@ -6071,7 +6089,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 		_field->setMaxHeight(computeMaxFieldHeight());
 		_kbShown = false;
 		_kbReplyTo = nullptr;
-		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) {
+		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyTo && !_editMsgId) {
 			_fieldBarCancel->hide();
 			updateMouseTracking();
 		}
@@ -6093,7 +6111,7 @@ void HistoryWidget::botCallbackSent(not_null<HistoryItem*> item) {
 
 	session().data().requestItemRepaint(item);
 
-	if (_replyToId == item->id) {
+	if (_replyTo.messageId == item->fullId()) {
 		cancelReply();
 	}
 	if (_keyboard->singleUse()
@@ -6114,7 +6132,7 @@ int HistoryWidget::computeMaxFieldHeight() const {
 		- (_groupCallBar ? _groupCallBar->height() : 0)
 		- (_requestsBar ? _requestsBar->height() : 0)
 		- ((_editMsgId
-			|| replyToId()
+			|| replyTo()
 			|| readyToForward()
 			|| (_previewData && _previewData->pendingTill >= 0))
 			? st::historyReplyHeight
@@ -6175,7 +6193,7 @@ bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) {
 void HistoryWidget::mousePressEvent(QMouseEvent *e) {
 	const auto isReadyToForward = readyToForward();
 	const auto hasSecondLayer = (_editMsgId
-		|| _replyToId
+		|| _replyTo
 		|| isReadyToForward
 		|| _kbReplyTo);
 	_replyForwardPressed = hasSecondLayer && QRect(
@@ -6201,11 +6219,13 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
 			} else {
 				_forwardPanel->editOptions(controller()->uiShow());
 			}
+		} else if (replyTo() && replyTo().messageId.peer != _peer->id) {
+			// edit options
 		} else {
 			controller()->showPeerHistory(
 				_peer,
 				Window::SectionShow::Way::Forward,
-				_editMsgId ? _editMsgId : replyToId());
+				_editMsgId ? _editMsgId : replyTo().messageId.msg);
 		}
 	}
 }
@@ -6235,7 +6255,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
 		if (item
 			&& _field->empty()
 			&& !_editMsgId
-			&& !_replyToId) {
+			&& !_replyTo) {
 			editMessage(item);
 			return;
 		}
@@ -6303,14 +6323,17 @@ void HistoryWidget::handlePeerMigration() {
 }
 
 bool HistoryWidget::replyToPreviousMessage() {
-	if (!_history || _editMsgId || _history->isForum()) {
+	if (!_history
+		|| _editMsgId
+		|| _history->isForum()
+		|| (_replyTo && _replyTo.messageId.peer != _history->peer->id)) {
 		return false;
 	}
 	const auto fullId = FullMsgId(
 		_history->peer->id,
-		_field->isVisible()
-			? _replyToId
-			: _highlighter.latestSingleHighlightedMsgId());
+		(_field->isVisible()
+			? _replyTo.messageId.msg
+			: _highlighter.latestSingleHighlightedMsgId()));
 	if (const auto item = session().data().message(fullId)) {
 		if (const auto view = item->mainView()) {
 			if (const auto previousView = view->previousDisplayedInBlocks()) {
@@ -6334,14 +6357,17 @@ bool HistoryWidget::replyToPreviousMessage() {
 }
 
 bool HistoryWidget::replyToNextMessage() {
-	if (!_history || _editMsgId || _history->isForum()) {
+	if (!_history
+		|| _editMsgId
+		|| _history->isForum()
+		|| (_replyTo && _replyTo.messageId.peer != _history->peer->id)) {
 		return false;
 	}
 	const auto fullId = FullMsgId(
 		_history->peer->id,
-		_field->isVisible()
-			? _replyToId
-			: _highlighter.latestSingleHighlightedMsgId());
+		(_field->isVisible()
+			? _replyTo.messageId.msg
+			: _highlighter.latestSingleHighlightedMsgId()));
 	if (const auto item = session().data().message(fullId)) {
 		if (const auto view = item->mainView()) {
 			if (const auto nextView = view->nextDisplayedInBlocks()) {
@@ -7024,17 +7050,19 @@ void HistoryWidget::clearFieldText(
 	setFieldText(TextWithTags(), events, fieldHistoryAction);
 }
 
-void HistoryWidget::replyToMessage(FullMsgId itemId) {
-	if (const auto item = session().data().message(itemId)) {
-		replyToMessage(item);
+void HistoryWidget::replyToMessage(FullReplyTo id) {
+	if (const auto item = session().data().message(id.messageId)) {
+		replyToMessage(item, id.quote);
 	}
 }
 
-void HistoryWidget::replyToMessage(not_null<HistoryItem*> item) {
+void HistoryWidget::replyToMessage(
+		not_null<HistoryItem*> item,
+		TextWithTags quote) {
 	if (isJoinChannel()) {
 		return;
 	}
-	_processingReplyId = item->id;
+	_processingReplyTo = { .messageId = item->fullId(), .quote = quote};
 	_processingReplyItem = item;
 	processReply();
 }
@@ -7042,14 +7070,13 @@ void HistoryWidget::replyToMessage(not_null<HistoryItem*> item) {
 void HistoryWidget::processReply() {
 	const auto processContinue = [=] {
 		return crl::guard(_list, [=] {
-			if (!_peer || !_processingReplyId) {
+			if (!_peer || !_processingReplyTo) {
 				return;
 			} else if (!_processingReplyItem) {
 				_processingReplyItem = _peer->owner().message(
-					_peer,
-					_processingReplyId);
+					_processingReplyTo.messageId);
 				if (!_processingReplyItem) {
-					_processingReplyId = 0;
+					_processingReplyTo = {};
 				} else {
 					processReply();
 				}
@@ -7057,16 +7084,16 @@ void HistoryWidget::processReply() {
 		});
 	};
 	const auto processCancel = [=] {
-		_processingReplyId = 0;
+		_processingReplyTo = {};
 		_processingReplyItem = nullptr;
 	};
 
-	if (!_peer || !_processingReplyId) {
+	if (!_peer || !_processingReplyTo) {
 		return processCancel();
 	} else if (!_processingReplyItem) {
 		session().api().requestMessageData(
-			_peer,
-			_processingReplyId,
+			session().data().peer(_processingReplyTo.messageId.peer),
+			_processingReplyTo.messageId.msg,
 			processContinue());
 		return;
 	} else if (_processingReplyItem->history() == _migrated) {
@@ -7107,7 +7134,7 @@ void HistoryWidget::processReply() {
 }
 
 void HistoryWidget::setReplyFieldsFromProcessing() {
-	if (!_processingReplyId || !_processingReplyItem) {
+	if (!_processingReplyTo || !_processingReplyItem) {
 		return;
 	}
 
@@ -7116,22 +7143,21 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
 		_composeSearch->hideAnimated();
 	}
 
-	const auto id = base::take(_processingReplyId);
+	const auto id = base::take(_processingReplyTo);
 	const auto item = base::take(_processingReplyItem);
 	if (_editMsgId) {
 		if (const auto localDraft = _history->localDraft({})) {
-			localDraft->msgId = id;
+			localDraft->reply = id;
 		} else {
 			_history->setLocalDraft(std::make_unique<Data::Draft>(
 				TextWithTags(),
 				id,
-				MsgId(),
 				MessageCursor(),
 				Data::PreviewState::Allowed));
 		}
 	} else {
 		_replyEditMsg = item;
-		_replyToId = id;
+		_replyTo = id;
 		updateReplyEditText(_replyEditMsg);
 		updateCanSendMessage();
 		updateBotKeyboard();
@@ -7170,11 +7196,10 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
 		_send->clearState();
 	}
 	if (!_editMsgId) {
-		if (_replyToId || !_field->empty()) {
+		if (_replyTo || !_field->empty()) {
 			_history->setLocalDraft(std::make_unique<Data::Draft>(
 				_field,
-				_replyToId,
-				MsgId(), // topicRootId
+				_replyTo,
 				_previewState));
 		} else {
 			_history->clearLocalDraft({});
@@ -7198,8 +7223,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
 		: Data::PreviewState::EmptyOnEdit;
 	_history->setLocalEditDraft(std::make_unique<Data::Draft>(
 		editData,
-		item->id,
-		MsgId(), // topicRootId
+		FullReplyTo{ item->fullId() },
 		cursor,
 		previewState));
 	applyDraft();
@@ -7261,17 +7285,18 @@ bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const {
 bool HistoryWidget::lastForceReplyReplied() const {
 	return _peer
 		&& _keyboard->forceReply()
-		&& _keyboard->forMsgId() == FullMsgId(_peer->id, _history->lastKeyboardId)
-		&& _keyboard->forMsgId().msg == replyToId();
+		&& _keyboard->forMsgId() == replyTo().messageId
+		&& (_keyboard->forMsgId()
+			== FullMsgId(_peer->id, _history->lastKeyboardId));
 }
 
 bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
 	bool wasReply = false;
-	if (_replyToId) {
+	if (_replyTo) {
 		wasReply = true;
 
 		_processingReplyItem = _replyEditMsg = nullptr;
-		_processingReplyId = _replyToId = 0;
+		_processingReplyTo = _replyTo = FullReplyTo();
 		mouseMoveEvent(0);
 		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
 			_fieldBarCancel->hide();
@@ -7284,11 +7309,11 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
 		updateControlsGeometry();
 		update();
 	} else if (const auto localDraft = (_history ? _history->localDraft({}) : nullptr)) {
-		if (localDraft->msgId) {
+		if (localDraft->reply) {
 			if (localDraft->textWithTags.text.isEmpty()) {
 				_history->clearLocalDraft({});
 			} else {
-				localDraft->msgId = 0;
+				localDraft->reply = {};
 			}
 		}
 	}
@@ -7344,7 +7369,7 @@ void HistoryWidget::cancelEdit() {
 	saveDraft();
 
 	mouseMoveEvent(nullptr);
-	if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) {
+	if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyTo()) {
 		_fieldBarCancel->hide();
 		updateMouseTracking();
 	}
@@ -7376,7 +7401,7 @@ void HistoryWidget::cancelFieldAreaState() {
 		cancelEdit();
 	} else if (readyToForward()) {
 		_history->setForwardDraft(MsgId(), {});
-	} else if (_replyToId) {
+	} else if (_replyTo) {
 		cancelReply();
 	} else if (_kbReplyTo) {
 		toggleKeyboard();
@@ -7512,7 +7537,7 @@ void HistoryWidget::updatePreview() {
 				preview.description,
 				Ui::DialogTextOptions());
 		}
-	} else if (!readyToForward() && !replyToId() && !_editMsgId) {
+	} else if (!readyToForward() && !replyTo() && !_editMsgId) {
 		_fieldBarCancel->hide();
 		updateMouseTracking();
 	}
@@ -7579,7 +7604,7 @@ bool HistoryWidget::updateCanSendMessage() {
 	if (!_peer) {
 		return false;
 	}
-	const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0;
+	const auto replyTo = (_replyTo && !_editMsgId) ? _replyEditMsg : 0;
 	const auto topic = replyTo ? replyTo->topic() : nullptr;
 	const auto allWithoutPolls = Data::AllSendRestrictions()
 		& ~ChatRestriction::SendPolls;
@@ -7657,7 +7682,7 @@ void HistoryWidget::escape() {
 		}
 	} else if (!_fieldAutocomplete->isHidden()) {
 		_fieldAutocomplete->hideAnimated();
-	} else if (_replyToId && _field->getTextWithTags().text.isEmpty()) {
+	} else if (_replyTo && _field->getTextWithTags().text.isEmpty()) {
 		cancelReply();
 	} else if (auto &voice = _voiceRecordBar; voice->isActive()) {
 		voice->showDiscardBox(nullptr, anim::type::normal);
@@ -7752,7 +7777,8 @@ void HistoryWidget::messageDataReceived(
 		MsgId msgId) {
 	if (!_peer || _peer != peer || !msgId) {
 		return;
-	} else if (_editMsgId == msgId || _replyToId == msgId) {
+	} else if (_editMsgId == msgId
+		|| (_replyTo.messageId == FullMsgId(peer->id, msgId))) {
 		updateReplyEditTexts(true);
 	}
 }
@@ -7775,14 +7801,14 @@ void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
 
 void HistoryWidget::updateReplyEditTexts(bool force) {
 	if (!force) {
-		if (_replyEditMsg || (!_editMsgId && !_replyToId)) {
+		if (_replyEditMsg || (!_editMsgId && !_replyTo)) {
 			return;
 		}
 	}
 	if (!_replyEditMsg && _peer) {
 		_replyEditMsg = session().data().message(
-			_peer->id,
-			_editMsgId ? _editMsgId : _replyToId);
+			_editMsgId ? _peer->id : _replyTo.messageId.peer,
+			_editMsgId ? _editMsgId : _replyTo.messageId.msg);
 	}
 	if (_replyEditMsg) {
 		const auto media = _replyEditMsg->media();
@@ -7828,7 +7854,7 @@ void HistoryWidget::updateForwarding() {
 void HistoryWidget::updateReplyToName() {
 	if (_editMsgId) {
 		return;
-	} else if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) {
+	} else if (!_replyEditMsg && (_replyTo || !_kbReplyTo)) {
 		return;
 	}
 	const auto from = [&] {
@@ -7862,8 +7888,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
 	auto backy = _field->y() - st::historySendPadding;
 	auto backh = fieldHeight() + 2 * st::historySendPadding;
 	auto hasForward = readyToForward();
-	auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo;
-	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
+	auto drawMsgText = (_editMsgId || _replyTo) ? _replyEditMsg : _kbReplyTo;
+	if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) {
 		if (!_editMsgId
 			&& drawMsgText
 			&& (_replyToNameVersion
@@ -7898,7 +7924,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
 		});
 	}
 
-	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
+	if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) {
 		const auto now = crl::now();
 		const auto paused = p.inactive();
 		const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
@@ -8097,7 +8123,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
 		const auto restrictionHidden = fieldOrDisabledShown()
 			|| isRecording();
 		if (restrictionHidden
-			|| replyToId()
+			|| replyTo()
 			|| readyToForward()
 			|| _kbShown) {
 			drawField(p, clip);
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index 5011c52cb..84e4b8be9 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -182,12 +182,14 @@ public:
 	MessageIdsList getSelectedItems() const;
 	void itemEdited(not_null<HistoryItem*> item);
 
-	void replyToMessage(FullMsgId itemId);
-	void replyToMessage(not_null<HistoryItem*> item);
+	void replyToMessage(FullReplyTo id);
+	void replyToMessage(
+		not_null<HistoryItem*> item,
+		TextWithTags quote = {});
 	void editMessage(FullMsgId itemId);
 	void editMessage(not_null<HistoryItem*> item);
 
-	MsgId replyToId() const;
+	[[nodiscard]] FullReplyTo replyTo() const;
 	bool lastForceReplyReplied(const FullMsgId &replyTo) const;
 	bool lastForceReplyReplied() const;
 	bool cancelReply(bool lastKeyboardUsed = false);
@@ -204,7 +206,7 @@ public:
 	void escape();
 
 	void sendBotCommand(const Bot::SendCommandRequest &request);
-	void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo);
+	void hideSingleUseKeyboard(FullMsgId replyToId);
 	bool insertBotCommand(const QString &cmd);
 
 	bool eventFilter(QObject *obj, QEvent *e) override;
@@ -633,11 +635,11 @@ private:
 	void searchInChat();
 
 	MTP::Sender _api;
-	MsgId _replyToId = 0;
+	FullReplyTo _replyTo;
 	Ui::Text::String _replyToName;
 	int _replyToNameVersion = 0;
 
-	MsgId _processingReplyId = 0;
+	FullReplyTo _processingReplyTo;
 	HistoryItem *_processingReplyItem = nullptr;
 
 	MsgId _editMsgId = 0;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index d19da9a57..56f6cf752 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -353,7 +353,7 @@ public:
 	void init();
 
 	void editMessage(FullMsgId id, bool photoEditAllowed = false);
-	void replyToMessage(FullMsgId id);
+	void replyToMessage(FullReplyTo id);
 	void updateForwarding(
 		Data::Thread *thread,
 		Data::ResolvedForwardDraft items);
@@ -367,7 +367,7 @@ public:
 	[[nodiscard]] bool isEditingMessage() const;
 	[[nodiscard]] bool readyToForward() const;
 	[[nodiscard]] const HistoryItemsList &forwardItems() const;
-	[[nodiscard]] FullMsgId replyingToMessage() const;
+	[[nodiscard]] FullReplyTo replyingToMessage() const;
 	[[nodiscard]] FullMsgId editMsgId() const;
 	[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
 	[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
@@ -375,7 +375,7 @@ public:
 	[[nodiscard]] MessageToEdit queryToEdit();
 	[[nodiscard]] WebPageId webPageId() const;
 
-	[[nodiscard]] MsgId getDraftMessageId() const;
+	[[nodiscard]] FullReplyTo getDraftReply() const;
 	[[nodiscard]] rpl::producer<> editCancelled() const {
 		return _editCancelled.events();
 	}
@@ -425,7 +425,7 @@ private:
 	rpl::lifetime _previewLifetime;
 
 	rpl::variable<FullMsgId> _editMsgId;
-	rpl::variable<FullMsgId> _replyToId;
+	rpl::variable<FullReplyTo> _replyTo;
 	std::unique_ptr<ForwardPanel> _forwardPanel;
 	rpl::producer<> _toForwardUpdated;
 
@@ -508,14 +508,14 @@ void FieldHeader::init() {
 
 	_editMsgId.value(
 	) | rpl::start_with_next([=](FullMsgId value) {
-		const auto shown = value ? value : _replyToId.current();
+		const auto shown = value ? value : _replyTo.current().messageId;
 		setShownMessage(_data->message(shown));
 	}, lifetime());
 
-	_replyToId.value(
-	) | rpl::start_with_next([=](FullMsgId value) {
+	_replyTo.value(
+	) | rpl::start_with_next([=](const FullReplyTo &value) {
 		if (!_editMsgId.current()) {
-			setShownMessage(_data->message(value));
+			setShownMessage(_data->message(value.messageId));
 		}
 	}, lifetime());
 
@@ -529,7 +529,7 @@ void FieldHeader::init() {
 			if (_editMsgId.current() == update.item->fullId()) {
 				_editCancelled.fire({});
 			}
-			if (_replyToId.current() == update.item->fullId()) {
+			if (_replyTo.current().messageId == update.item->fullId()) {
 				_replyCancelled.fire({});
 			}
 		} else {
@@ -545,7 +545,7 @@ void FieldHeader::init() {
 			_editCancelled.fire({});
 		} else if (readyToForward()) {
 			_forwardCancelled.fire({});
-		} else if (_replyToId.current()) {
+		} else if (_replyTo.current()) {
 			_replyCancelled.fire({});
 		}
 		updateVisible();
@@ -624,7 +624,7 @@ void FieldHeader::init() {
 				} else {
 					auto id = isEditingMessage()
 						? _editMsgId.current()
-						: replyingToMessage();
+						: replyingToMessage().messageId;
 					_scrollToItemRequests.fire(std::move(id));
 				}
 			}
@@ -692,16 +692,18 @@ void FieldHeader::setShownMessage(HistoryItem *item) {
 }
 
 void FieldHeader::resolveMessageData() {
-	const auto id = (isEditingMessage() ? _editMsgId : _replyToId).current();
+	const auto id = isEditingMessage()
+		? _editMsgId.current()
+		: _replyTo.current().messageId;
 	if (!id) {
 		return;
 	}
 	const auto peer = _data->peer(id.peer);
 	const auto itemId = id.msg;
 	const auto callback = crl::guard(this, [=] {
-		const auto now = (isEditingMessage()
-			? _editMsgId
-			: _replyToId).current();
+		const auto now = isEditingMessage()
+			? _editMsgId.current()
+			: _replyTo.current().messageId;
 		if (now == id && !_shownMessage) {
 			if (const auto message = _data->message(peer, itemId)) {
 				setShownMessage(message);
@@ -950,8 +952,8 @@ const HistoryItemsList &FieldHeader::forwardItems() const {
 	return _forwardPanel->items();
 }
 
-FullMsgId FieldHeader::replyingToMessage() const {
-	return _replyToId.current();
+FullReplyTo FieldHeader::replyingToMessage() const {
+	return _replyTo.current();
 }
 
 bool FieldHeader::hasPreview() const {
@@ -962,8 +964,10 @@ WebPageId FieldHeader::webPageId() const {
 	return hasPreview() ? _preview.data->id : CancelledWebPageId;
 }
 
-MsgId FieldHeader::getDraftMessageId() const {
-	return (isEditingMessage() ? _editMsgId : _replyToId).current().msg;
+FullReplyTo FieldHeader::getDraftReply() const {
+	return isEditingMessage()
+		? FullReplyTo{ _editMsgId.current() }
+		: _replyTo.current();
 }
 
 void FieldHeader::updateControlsGeometry(QSize size) {
@@ -992,8 +996,8 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) {
 	update();
 }
 
-void FieldHeader::replyToMessage(FullMsgId id) {
-	_replyToId = id;
+void FieldHeader::replyToMessage(FullReplyTo id) {
+	_replyTo = id;
 }
 
 void FieldHeader::updateForwarding(
@@ -1156,6 +1160,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
 	}
 	unregisterDraftSources();
 	_history = history;
+	_topicRootId = args.topicRootId;
 	_historyLifetime.destroy();
 	_header->setHistory(args);
 	registerDraftSource();
@@ -1193,8 +1198,10 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
 	orderControls();
 }
 
-void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) {
+void ComposeControls::setCurrentDialogsEntryState(
+		Dialogs::EntryState state) {
 	unregisterDraftSources();
+	state.currentReplyTo.topicRootId = _topicRootId;
 	_currentDialogsEntryState = state;
 	updateForwarding();
 	registerDraftSource();
@@ -1472,16 +1479,12 @@ void ComposeControls::saveFieldToHistoryLocalDraft() {
 	if (!_history || !key) {
 		return;
 	}
-	const auto id = _header->getDraftMessageId();
+	const auto id = _header->getDraftReply();
 	if (_preview && (id || !_field->empty())) {
 		const auto key = draftKeyCurrent();
 		_history->setDraft(
 			key,
-			std::make_unique<Data::Draft>(
-				_field,
-				_header->getDraftMessageId(),
-				key.topicRootId(),
-				_preview->state()));
+			std::make_unique<Data::Draft>(_field, id, _preview->state()));
 	} else {
 		_history->clearDraft(draftKeyCurrent());
 	}
@@ -1757,7 +1760,7 @@ void ComposeControls::initKeyHandler() {
 					}
 				}
 				_replyNextRequests.fire({
-					.replyId = replyingToMessage(),
+					.replyId = replyingToMessage().messageId,
 					.direction = (isDown
 						? ReplyNextRequest::Direction::Next
 						: ReplyNextRequest::Direction::Previous)
@@ -2037,8 +2040,8 @@ Data::DraftKey ComposeControls::draftKey(DraftType type) const {
 	case Section::History:
 	case Section::Replies:
 		return (type == DraftType::Edit)
-			? Key::LocalEdit(_currentDialogsEntryState.rootId)
-			: Key::Local(_currentDialogsEntryState.rootId);
+			? Key::LocalEdit(_topicRootId)
+			: Key::Local(_topicRootId);
 	case Section::Scheduled:
 		return (type == DraftType::Edit)
 			? Key::ScheduledEdit()
@@ -2102,7 +2105,7 @@ void ComposeControls::registerDraftSource() {
 	if (key != Data::DraftKey::None()) {
 		const auto draft = [=] {
 			return Storage::MessageDraft{
-				_header->getDraftMessageId(),
+				_header->getDraftReply(),
 				_field->getTextWithTags(),
 				_preview->state(),
 			};
@@ -2150,8 +2153,8 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
 	const auto draft = editDraft
 		? editDraft
 		: _history->draft(draftKey(DraftType::Normal));
-	const auto editingId = (draft == editDraft)
-		? FullMsgId{ _history->peer->id, draft ? draft->msgId : 0 }
+	const auto editingId = (draft && draft == editDraft)
+		? draft->reply.messageId
 		: FullMsgId();
 
 	InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); });
@@ -2232,7 +2235,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
 	} else {
 		_canReplaceMedia = false;
 		_photoEditMedia = nullptr;
-		_header->replyToMessage({ _history->peer->id, draft->msgId });
+		_header->replyToMessage(draft->reply);
 		if (_header->replyingToMessage()) {
 			cancelForward();
 		}
@@ -2241,9 +2244,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
 }
 
 void ComposeControls::cancelForward() {
-	_history->setForwardDraft(
-		_currentDialogsEntryState.rootId,
-		{});
+	_history->setForwardDraft(_topicRootId, {});
 	updateForwarding();
 }
 
@@ -2840,8 +2841,7 @@ void ComposeControls::toggleTabbedSelectorMode() {
 				&& !_regularWindow->adaptive().isOneColumn()) {
 			Core::App().settings().setTabbedSelectorSectionEnabled(true);
 			Core::App().saveSettingsDelayed();
-			const auto topic = _history->peer->forumTopicFor(
-				_currentDialogsEntryState.rootId);
+			const auto topic = _history->peer->forumTopicFor(_topicRootId);
 			pushTabbedSelectorToThirdSection(
 				(topic ? topic : (Data::Thread*)_history),
 				Window::SectionShow::Way::ClearStack);
@@ -2900,8 +2900,10 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
 		key,
 		std::make_unique<Data::Draft>(
 			editData,
-			item->id,
-			key.topicRootId(),
+			FullReplyTo{
+				.messageId = item->fullId(),
+				.topicRootId = key.topicRootId(),
+			},
 			cursor,
 			previewState));
 	applyDraft();
@@ -2968,25 +2970,26 @@ void ComposeControls::maybeCancelEditMessage() {
 	}
 }
 
-void ComposeControls::replyToMessage(FullMsgId id) {
+void ComposeControls::replyToMessage(FullReplyTo id) {
 	Expects(_history != nullptr);
 	Expects(draftKeyCurrent() != Data::DraftKey::None());
 
+	id.topicRootId = _topicRootId;
 	if (!id) {
 		cancelReplyMessage();
 		return;
 	}
 	if (isEditingMessage()) {
 		const auto key = draftKey(DraftType::Normal);
+		Assert(key.topicRootId() == id.topicRootId);
 		if (const auto localDraft = _history->draft(key)) {
-			localDraft->msgId = id.msg;
+			localDraft->reply = id;
 		} else {
 			_history->setDraft(
 				key,
 				std::make_unique<Data::Draft>(
 					TextWithTags(),
-					id.msg,
-					key.topicRootId(),
+					id,
 					MessageCursor(),
 					Data::PreviewState::Allowed));
 		}
@@ -3008,11 +3011,11 @@ void ComposeControls::cancelReplyMessage() {
 	if (_history) {
 		const auto key = draftKey(DraftType::Normal);
 		if (const auto localDraft = _history->draft(key)) {
-			if (localDraft->msgId) {
+			if (localDraft->reply.messageId) {
 				if (localDraft->textWithTags.text.isEmpty()) {
 					_history->clearDraft(key);
 				} else {
-					localDraft->msgId = 0;
+					localDraft->reply = {};
 				}
 			}
 		}
@@ -3025,7 +3028,7 @@ void ComposeControls::cancelReplyMessage() {
 }
 
 void ComposeControls::updateForwarding() {
-	const auto rootId = _currentDialogsEntryState.rootId;
+	const auto rootId = _topicRootId;
 	const auto thread = (_history && rootId)
 		? _history->peer->forumTopicFor(rootId)
 		: (Data::Thread*)_history;
@@ -3118,7 +3121,7 @@ void ComposeControls::initForwardProcess() {
 	) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
 		if (const auto topic = update.entry->asTopic()) {
 			if (topic->history() == _history
-				&& topic->rootId() == _currentDialogsEntryState.rootId) {
+				&& topic->rootId() == _topicRootId) {
 				updateForwarding();
 			}
 		}
@@ -3145,8 +3148,10 @@ bool ComposeControls::isEditingMessage() const {
 	return _header->isEditingMessage();
 }
 
-FullMsgId ComposeControls::replyingToMessage() const {
-	return _header->replyingToMessage();
+FullReplyTo ComposeControls::replyingToMessage() const {
+	auto result = _header->replyingToMessage();
+	result.topicRootId = _topicRootId;
+	return result;
 }
 
 bool ComposeControls::readyToForward() const {
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
index 3dc5e46fa..b98cbca83 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
@@ -185,7 +185,7 @@ public:
 	[[nodiscard]] bool isEditingMessage() const;
 	[[nodiscard]] bool readyToForward() const;
 	[[nodiscard]] const HistoryItemsList &forwardItems() const;
-	[[nodiscard]] FullMsgId replyingToMessage() const;
+	[[nodiscard]] FullReplyTo replyingToMessage() const;
 
 	[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
 
@@ -198,7 +198,7 @@ public:
 	void cancelEditMessage();
 	void maybeCancelEditMessage(); // Confirm if changed and cancel.
 
-	void replyToMessage(FullMsgId id);
+	void replyToMessage(FullReplyTo id);
 	void cancelReplyMessage();
 
 	void updateForwarding();
@@ -345,6 +345,7 @@ private:
 	rpl::event_stream<ChatHelpers::FileChosen> _stickerOrEmojiChosen;
 
 	History *_history = nullptr;
+	MsgId _topicRootId = 0;
 	Fn<bool()> _showSlowmodeError;
 	Fn<Api::SendAction()> _sendActionFactory;
 	rpl::variable<int> _slowmodeSecondsLeft;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
index f74453919..d1bf5c965 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
@@ -117,7 +117,7 @@ void ForwardPanel::checkTexts() {
 		: kNameNoCaptionsVersion;
 	if (keepNames) {
 		for (const auto item : _data.items) {
-			if (const auto from = item->senderOriginal()) {
+			if (const auto from = item->originalSender()) {
 				version += from->nameVersion();
 			} else if (const auto info = item->hiddenSenderInfo()) {
 				++version;
@@ -154,7 +154,7 @@ void ForwardPanel::updateTexts() {
 		auto names = std::vector<QString>();
 		names.reserve(_data.items.size());
 		for (const auto item : _data.items) {
-			if (const auto from = item->senderOriginal()) {
+			if (const auto from = item->originalSender()) {
 				if (!insertedPeers.contains(from)) {
 					insertedPeers.emplace(from);
 					names.push_back(from->shortName());
diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
index 60c6e8191..57862872a 100644
--- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
@@ -655,7 +655,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
 	}
 	if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
 		 if (!msgsigned->isAnonymousRank) {
-			result.author = msgsigned->author;
+			result.author = msgsigned->postAuthor;
 		 }
 	}
 	if (message->displayedEditDate()) {
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 7bc9c5b57..3073d3495 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -601,7 +601,7 @@ bool AddReplyToMessageAction(
 		if (!item) {
 			return;
 		}
-		list->replyToMessageRequestNotify(item->fullId());
+		list->replyToMessageRequestNotify({ item->fullId() });
 	}, &st::menuIconReply);
 	return true;
 }
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index aab999319..58e1676b0 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -68,10 +68,10 @@ Element *MousedElement/* = nullptr*/;
 		HistoryMessageForwarded *prevForwarded,
 		not_null<HistoryItem*> item,
 		HistoryMessageForwarded *forwarded) {
-	const auto sender = previous->senderOriginal();
+	const auto sender = previous->originalSender();
 	if ((prevForwarded != nullptr) != (forwarded != nullptr)) {
 		return false;
-	} else if (sender != item->senderOriginal()) {
+	} else if (sender != item->originalSender()) {
 		return false;
 	} else if (!prevForwarded || sender) {
 		return true;
@@ -178,7 +178,7 @@ bool DefaultElementDelegate::elementIsChatWide() {
 	return false;
 }
 
-void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) {
+void DefaultElementDelegate::elementReplyTo(const FullReplyTo &to) {
 }
 
 void DefaultElementDelegate::elementStartInteraction(
@@ -275,8 +275,10 @@ QString DateTooltipText(not_null<Element*> view) {
 	}
 	if (view->isSignedAuthorElided()) {
 		if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
-			dateText += '\n'
-				+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
+			dateText += '\n' + tr::lng_signed_author(
+				tr::now,
+				lt_user,
+				msgsigned->postAuthor);
 		}
 	}
 	return dateText;
@@ -1447,7 +1449,7 @@ void Element::unloadHeavyPart() {
 		_heavyCustomEmoji = false;
 		_text.unloadPersistentAnimation();
 		if (const auto reply = data()->Get<HistoryMessageReply>()) {
-			reply->replyToText.unloadPersistentAnimation();
+			reply->unloadPersistentAnimation();
 		}
 	}
 }
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index 4f62303c7..02635118a 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -100,7 +100,7 @@ public:
 	virtual void elementHandleViaClick(not_null<UserData*> bot) = 0;
 	virtual bool elementIsChatWide() = 0;
 	virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
-	virtual void elementReplyTo(const FullMsgId &to) = 0;
+	virtual void elementReplyTo(const FullReplyTo &to) = 0;
 	virtual void elementStartInteraction(not_null<const Element*> view) = 0;
 	virtual void elementStartPremium(
 		not_null<const Element*> view,
@@ -149,7 +149,7 @@ public:
 		const FullMsgId &context) override;
 	void elementHandleViaClick(not_null<UserData*> bot) override;
 	bool elementIsChatWide() override;
-	void elementReplyTo(const FullMsgId &to) override;
+	void elementReplyTo(const FullReplyTo &to) override;
 	void elementStartInteraction(not_null<const Element*> view) override;
 	void elementStartPremium(
 		not_null<const Element*> view,
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 90924281c..f2ed79c39 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -1735,7 +1735,7 @@ not_null<Ui::PathShiftGradient*> ListWidget::elementPathShiftGradient() {
 	return _pathGradient.get();
 }
 
-void ListWidget::elementReplyTo(const FullMsgId &to) {
+void ListWidget::elementReplyTo(const FullReplyTo &to) {
 	replyToMessageRequestNotify(to);
 }
 
@@ -2474,7 +2474,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
 		mouseActionCancel();
 		switch (CurrentQuickAction()) {
 		case DoubleClickQuickAction::Reply: {
-			replyToMessageRequestNotify(_overElement->data()->fullId());
+			replyToMessageRequestNotify({ _overElement->data()->fullId() });
 		} break;
 		case DoubleClickQuickAction::React: {
 			toggleFavoriteReaction(_overElement);
@@ -3855,12 +3855,12 @@ bool ListWidget::lastMessageEditRequestNotify() const {
 	}
 }
 
-rpl::producer<FullMsgId> ListWidget::replyToMessageRequested() const {
+rpl::producer<FullReplyTo> ListWidget::replyToMessageRequested() const {
 	return _requestedToReplyToMessage.events();
 }
 
-void ListWidget::replyToMessageRequestNotify(FullMsgId item) {
-	_requestedToReplyToMessage.fire(std::move(item));
+void ListWidget::replyToMessageRequestNotify(FullReplyTo id) {
+	_requestedToReplyToMessage.fire(std::move(id));
 }
 
 rpl::producer<FullMsgId> ListWidget::readMessageRequested() const {
@@ -3878,10 +3878,10 @@ void ListWidget::replyNextMessage(FullMsgId fullId, bool next) {
 			if (!view->data()->isRegular()) {
 				return replyNextMessage(newFullId, next);
 			}
-			replyToMessageRequestNotify(newFullId);
+			replyToMessageRequestNotify({ newFullId });
 			_requestedToShowMessage.fire_copy(newFullId);
 		} else {
-			replyToMessageRequestNotify(FullMsgId());
+			replyToMessageRequestNotify({});
 			_highlighter.clear();
 		}
 	};
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index 704fb1685..138f39cca 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -277,8 +277,8 @@ public:
 	[[nodiscard]] rpl::producer<FullMsgId> editMessageRequested() const;
 	void editMessageRequestNotify(FullMsgId item) const;
 	[[nodiscard]] bool lastMessageEditRequestNotify() const;
-	[[nodiscard]] rpl::producer<FullMsgId> replyToMessageRequested() const;
-	void replyToMessageRequestNotify(FullMsgId item);
+	[[nodiscard]] rpl::producer<FullReplyTo> replyToMessageRequested() const;
+	void replyToMessageRequestNotify(FullReplyTo id);
 	[[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const;
 	[[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const;
 	void replyNextMessage(FullMsgId fullId, bool next = true);
@@ -323,7 +323,7 @@ public:
 	void elementHandleViaClick(not_null<UserData*> bot) override;
 	bool elementIsChatWide() override;
 	not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
-	void elementReplyTo(const FullMsgId &to) override;
+	void elementReplyTo(const FullReplyTo &to) override;
 	void elementStartInteraction(not_null<const Element*> view) override;
 	void elementStartPremium(
 		not_null<const Element*> view,
@@ -735,7 +735,7 @@ private:
 	base::Timer _touchScrollTimer;
 
 	rpl::event_stream<FullMsgId> _requestedToEditMessage;
-	rpl::event_stream<FullMsgId> _requestedToReplyToMessage;
+	rpl::event_stream<FullReplyTo> _requestedToReplyToMessage;
 	rpl::event_stream<FullMsgId> _requestedToReadMessage;
 	rpl::event_stream<FullMsgId> _requestedToShowMessage;
 
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index a50b92d27..87802b4f2 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -477,7 +477,7 @@ void Message::refreshRightBadge() {
 		} else if (data()->author()->isMegagroup()) {
 			if (const auto msgsigned = data()->Get<HistoryMessageSigned>()) {
 				Assert(msgsigned->isAnonymousRank);
-				return msgsigned->author;
+				return msgsigned->postAuthor;
 			}
 		}
 		const auto channel = data()->history()->peer->asMegagroup();
@@ -801,9 +801,9 @@ QSize Message::performCountOptimalSize() {
 				accumulate_max(maxWidth, namew);
 			}
 			if (reply) {
-				auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
-				if (reply->replyToVia) {
-					replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth;
+				auto replyw = st::msgPadding.left() + reply->maxWidth() - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
+				if (reply->originalVia) {
+					replyw += st::msgServiceFont->spacew + reply->originalVia->maxWidth;
 				}
 				accumulate_max(maxWidth, replyw);
 			}
@@ -1736,8 +1736,8 @@ void Message::clickHandlerPressedChanged(
 		toggleTopicButtonRipple(pressed);
 	} else if (_viewButton) {
 		_viewButton->checkLink(handler, pressed);
-	} else if (const auto reply = displayedReply();
-			reply && (handler == reply->replyToLink())) {
+	} else if (const auto reply = displayedReply()
+		; reply && (handler == reply->link())) {
 		toggleReplyRipple(pressed);
 	}
 }
@@ -2469,10 +2469,11 @@ bool Message::getStateReplyInfo(
 				trect.y() + st::msgReplyPadding.top(),
 				trect.width(),
 				st::msgReplyBarSize.height());
-			if ((reply->replyToMsg || reply->replyToStory)
-				&& g.contains(point)) {
-				outResult->link = reply->replyToLink();
-				reply->ripple.lastPoint = point - g.topLeft();
+			if (g.contains(point)) {
+				if (const auto link = reply->link()) {
+					outResult->link = reply->link();
+					reply->ripple.lastPoint = point - g.topLeft();
+				}
 			}
 			return true;
 		}
@@ -3439,7 +3440,7 @@ ClickHandlerPtr Message::fastReplyLink() const {
 	}
 	const auto itemId = data()->fullId();
 	_fastReplyLink = std::make_shared<LambdaClickHandler>([=] {
-		delegate()->elementReplyTo(itemId);
+		delegate()->elementReplyTo({ itemId });
 	});
 	return _fastReplyLink;
 }
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 7b878ec52..080984847 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -1002,7 +1002,7 @@ void RepliesWidget::sendingFilesConfirmed(
 			album,
 			action);
 	}
-	if (_composeControls->replyingToMessage().msg == action.replyTo.msgId) {
+	if (_composeControls->replyingToMessage() == action.replyTo) {
 		_composeControls->cancelReplyMessage();
 		refreshTopBarActiveChat();
 	}
@@ -1123,9 +1123,9 @@ bool RepliesWidget::showSendingFilesError(
 }
 
 Api::SendAction RepliesWidget::prepareSendAction(
-		Api::SendOptions options) const {
+	Api::SendOptions options) const {
 	auto result = Api::SendAction(_history, options);
-	result.replyTo = { .msgId = replyToId(), .topicRootId = _rootId };
+	result.replyTo = replyTo();
 	result.options.sendAs = _composeControls->sendAsPeer();
 	return result;
 }
@@ -1444,28 +1444,29 @@ SendMenu::Type RepliesWidget::sendMenuType() const {
 		: SendMenu::Type::Scheduled;
 }
 
+FullReplyTo RepliesWidget::replyTo() const {
+	if (auto custom = _composeControls->replyingToMessage()) {
+		custom.topicRootId = _rootId;
+		return custom;
+	}
+	return FullReplyTo{
+		.messageId = FullMsgId(_history->peer->id, _rootId),
+		.topicRootId = _rootId,
+	};
+}
+
 void RepliesWidget::refreshTopBarActiveChat() {
 	using namespace Dialogs;
 	const auto state = EntryState{
 		.key = (_topic ? Key{ _topic } : Key{ _history }),
 		.section = EntryState::Section::Replies,
-		.rootId = _rootId,
-		.currentReplyToId = _composeControls->replyingToMessage().msg,
+		.currentReplyTo = replyTo(),
 	};
 	_topBar->setActiveChat(state, _sendAction.get());
 	_composeControls->setCurrentDialogsEntryState(state);
 	controller()->setCurrentDialogsEntryState(state);
 }
 
-MsgId RepliesWidget::replyToId() const {
-	const auto custom = _composeControls->replyingToMessage().msg;
-	return custom
-		? custom
-		: (_rootId == Data::ForumTopic::kGeneralId)
-		? MsgId()
-		: _rootId;
-}
-
 void RepliesWidget::refreshUnreadCountBadge(std::optional<int> count) {
 	if (count.has_value()) {
 		_cornerButtons.updateJumpDownVisibility(count);
@@ -2052,8 +2053,8 @@ bool RepliesWidget::confirmSendingFiles(
 		insertTextOnCancel);
 }
 
-void RepliesWidget::replyToMessage(FullMsgId itemId) {
-	_composeControls->replyToMessage(itemId);
+void RepliesWidget::replyToMessage(FullReplyTo id) {
+	_composeControls->replyToMessage(std::move(id));
 	refreshTopBarActiveChat();
 }
 
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h
index b048dca36..14104e913 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h
@@ -243,7 +243,7 @@ private:
 		mtpRequestId *const saveEditMsgRequestId);
 	void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
 	[[nodiscard]] SendMenu::Type sendMenuType() const;
-	[[nodiscard]] MsgId replyToId() const;
+	[[nodiscard]] FullReplyTo replyTo() const;
 	[[nodiscard]] HistoryItem *lookupRoot() const;
 	[[nodiscard]] Data::ForumTopic *lookupTopic();
 	[[nodiscard]] bool computeAreComments() const;
@@ -252,7 +252,7 @@ private:
 	void pushReplyReturn(not_null<HistoryItem*> item);
 	void checkReplyReturns();
 	void recountChatWidth();
-	void replyToMessage(FullMsgId itemId);
+	void replyToMessage(FullReplyTo id);
 	void refreshTopBarActiveChat();
 	void refreshUnreadCountBadge(std::optional<int> count);
 
diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp
index 5a3ca97bc..cc7652f62 100644
--- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp
@@ -112,7 +112,7 @@ bool TranslateTracker::add(
 	}
 	if (!skipDependencies) {
 		if (const auto reply = item->Get<HistoryMessageReply>()) {
-			if (const auto to = reply->replyToMsg.get()) {
+			if (const auto to = reply->resolvedMessage.get()) {
 				add(to, true);
 			}
 		}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 52008c66f..2e01c30c9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -1069,7 +1069,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
 		if (reply) {
 			const auto replyRect = QRect(rectx, recty, rectw, recth);
 			if (replyRect.contains(point)) {
-				result.link = reply->replyToLink();
+				result.link = reply->link();
 				reply->ripple.lastPoint = point - replyRect.topLeft();
 				if (!reply->ripple.animation) {
 					reply->ripple.animation = std::make_unique<Ui::RippleAnimation>(
@@ -1737,7 +1737,7 @@ int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply
 		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
 	}
 	if (reply) {
-		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
+		accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth());
 	}
 	return result;
 }
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
index 4ff49e604..4f5e9a5c5 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
@@ -464,7 +464,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
 				if (reply) {
 					const auto replyRect = QRect(rectx, recty, rectw, recth);
 					if (replyRect.contains(point)) {
-						result.link = reply->replyToLink();
+						result.link = reply->link();
 						reply->ripple.lastPoint = point - replyRect.topLeft();
 						if (!reply->ripple.animation) {
 							reply->ripple.animation = std::make_unique<Ui::RippleAnimation>(
@@ -519,10 +519,9 @@ bool UnwrappedMedia::hasTextForCopy() const {
 	return _content->hasTextForCopy();
 }
 
-bool UnwrappedMedia::dragItemByHandler(
-		const ClickHandlerPtr &p) const {
+bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
 	const auto reply = _parent->displayedReply();
-	return !(reply && (reply->replyToLink() == p));
+	return !reply || (reply->link() != p);
 }
 
 QRect UnwrappedMedia::contentRectForReactions() const {
@@ -642,7 +641,7 @@ int UnwrappedMedia::additionalWidth(
 		accumulate_max(result, 2 * st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.right());
 	}
 	if (reply) {
-		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
+		accumulate_max(result, st::msgReplyPadding.left() + reply->maxWidth());
 	}
 	return result;
 }
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 3473738cf..d9a8d3606 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -602,8 +602,7 @@ bool MainWidget::shareUrl(
 	const auto topicRootId = thread->topicRootId();
 	history->setLocalDraft(std::make_unique<Data::Draft>(
 		textWithTags,
-		0, // replyTo
-		topicRootId,
+		FullReplyTo{ .topicRootId = topicRootId },
 		cursor,
 		Data::PreviewState::Allowed));
 	history->clearLocalEditDraft(topicRootId);
@@ -746,8 +745,8 @@ void MainWidget::sendBotCommand(Bot::SendCommandRequest request) {
 	}
 }
 
-void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
-	_history->hideSingleUseKeyboard(peer, replyTo);
+void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) {
+	_history->hideSingleUseKeyboard(replyToId);
 }
 
 void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) {
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index fa33b5d83..386434ecc 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -188,7 +188,7 @@ public:
 		not_null<const QMimeData*> data);
 
 	void sendBotCommand(Bot::SendCommandRequest request);
-	void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo);
+	void hideSingleUseKeyboard(FullMsgId replyToId);
 
 	void searchMessages(const QString &query, Dialogs::Key inChat);
 
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index d8544ffc5..e51098feb 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -3037,7 +3037,7 @@ void OverlayWidget::refreshMediaViewer() {
 
 void OverlayWidget::refreshFromLabel() {
 	if (_message) {
-		_from = _message->senderOriginal();
+		_from = _message->originalSender();
 		if (const auto info = _message->hiddenSenderInfo()) {
 			_fromName = info->name;
 		} else {
diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp
index 348a11591..8559bf621 100644
--- a/Telegram/SourceFiles/storage/storage_account.cpp
+++ b/Telegram/SourceFiles/storage/storage_account.cpp
@@ -59,6 +59,7 @@ constexpr auto kMultiDraftTagOld = quint64(0xFFFF'FFFF'FFFF'FF01ULL);
 constexpr auto kMultiDraftCursorsTagOld = quint64(0xFFFF'FFFF'FFFF'FF02ULL);
 constexpr auto kMultiDraftTag = quint64(0xFFFF'FFFF'FFFF'FF03ULL);
 constexpr auto kMultiDraftCursorsTag = quint64(0xFFFF'FFFF'FFFF'FF04ULL);
+constexpr auto kRichDraftsTag = quint64(0xFFFF'FFFF'FFFF'FF05ULL);
 
 enum { // Local Storage Keys
 	lskUserMap = 0x00,
@@ -1041,7 +1042,7 @@ void EnumerateDrafts(
 		}
 		callback(
 			key,
-			draft->msgId,
+			draft->reply,
 			draft->textWithTags,
 			draft->previewState,
 			draft->cursor);
@@ -1049,12 +1050,12 @@ void EnumerateDrafts(
 	for (const auto &[key, source] : sources) {
 		const auto draft = source.draft();
 		const auto cursor = source.cursor();
-		if (draft.msgId
+		if (draft.reply.messageId
 			|| !draft.textWithTags.text.isEmpty()
 			|| cursor != MessageCursor()) {
 			callback(
 				key,
-				draft.msgId,
+				draft.reply,
 				draft.textWithTags,
 				draft.previewState,
 				cursor);
@@ -1119,14 +1120,18 @@ void Account::writeDrafts(not_null<History*> history) {
 	auto size = int(sizeof(quint64) * 2 + sizeof(quint32));
 	const auto sizeCallback = [&](
 			auto&&, // key
-			MsgId, // msgId
+			const FullReplyTo &reply,
 			const TextWithTags &text,
 			Data::PreviewState,
 			auto&&) { // cursor
 		size += sizeof(qint64) // key
 			+ Serialize::stringSize(text.text)
 			+ sizeof(qint64) + TextUtilities::SerializeTagsSize(text.tags)
-			+ sizeof(qint64) + sizeof(qint32); // msgId, previewState
+			+ sizeof(qint64) + sizeof(qint64) // messageId
+			+ Serialize::stringSize(reply.quote.text)
+			+ sizeof(qint64)
+			+ TextUtilities::SerializeTagsSize(reply.quote.tags)
+			+ sizeof(qint32); // previewState
 	};
 	EnumerateDrafts(
 		map,
@@ -1136,13 +1141,13 @@ void Account::writeDrafts(not_null<History*> history) {
 
 	EncryptedDescriptor data(size);
 	data.stream
-		<< quint64(kMultiDraftTag)
+		<< quint64(kRichDraftsTag)
 		<< SerializePeerId(peerId)
 		<< quint32(count);
 
 	const auto writeCallback = [&](
 			const Data::DraftKey &key,
-			MsgId msgId,
+			const FullReplyTo &reply,
 			const TextWithTags &text,
 			Data::PreviewState previewState,
 			auto&&) { // cursor
@@ -1150,7 +1155,10 @@ void Account::writeDrafts(not_null<History*> history) {
 			<< key.serialize()
 			<< text.text
 			<< TextUtilities::SerializeTags(text.tags)
-			<< qint64(msgId.bare)
+			<< qint64(reply.messageId.peer.value)
+			<< qint64(reply.messageId.msg.bare)
+			<< reply.quote.text
+			<< TextUtilities::SerializeTags(reply.quote.tags)
 			<< qint32(previewState);
 	};
 	EnumerateDrafts(
@@ -1201,7 +1209,7 @@ void Account::writeDraftCursors(not_null<History*> history) {
 
 	const auto writeCallback = [&](
 			const Data::DraftKey &key,
-			MsgId, // msgId
+			auto&&, // reply
 			auto&&, // text
 			Data::PreviewState,
 			const MessageCursor &cursor) { // cursor
@@ -1343,7 +1351,9 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
 
 	quint64 tag = 0;
 	draft.stream >> tag;
-	if (tag != kMultiDraftTag && tag != kMultiDraftTagOld) {
+	if (tag != kRichDraftsTag
+		&& tag != kMultiDraftTag
+		&& tag != kMultiDraftTagOld) {
 		readDraftsWithCursorsLegacy(history, draft, tag);
 		return;
 	}
@@ -1359,24 +1369,43 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
 	}
 	auto map = Data::HistoryDrafts();
 	const auto keysOld = (tag == kMultiDraftTagOld);
+	const auto rich = (tag == kRichDraftsTag);
 	for (auto i = 0; i != count; ++i) {
-		TextWithTags data;
-		QByteArray tagsSerialized;
-		qint64 keyValue = 0, messageId = 0;
+		TextWithTags quote;
+		TextWithTags text;
+		QByteArray textTagsSerialized;
+		QByteArray quoteTagsSerialized;
+		qint64 keyValue = 0;
+		qint64 messageIdPeer = 0, messageIdMsg = 0;
 		qint32 keyValueOld = 0, uncheckedPreviewState = 0;
 		if (keysOld) {
 			draft.stream >> keyValueOld;
 		} else {
 			draft.stream >> keyValue;
 		}
-		draft.stream
-			>> data.text
-			>> tagsSerialized
-			>> messageId
-			>> uncheckedPreviewState;
-		data.tags = TextUtilities::DeserializeTags(
-			tagsSerialized,
-			data.text.size());
+		if (!rich) {
+			draft.stream
+				>> text.text
+				>> textTagsSerialized
+				>> messageIdMsg
+				>> uncheckedPreviewState;
+			messageIdPeer = peerId.value;
+		} else {
+			draft.stream
+				>> text.text
+				>> textTagsSerialized
+				>> messageIdPeer
+				>> messageIdMsg
+				>> quote.text
+				>> quoteTagsSerialized
+				>> uncheckedPreviewState;
+			quote.tags = TextUtilities::DeserializeTags(
+				quoteTagsSerialized,
+				quote.text.size());
+		}
+		text.tags = TextUtilities::DeserializeTags(
+			textTagsSerialized,
+			text.text.size());
 		auto previewState = Data::PreviewState::Allowed;
 		switch (static_cast<Data::PreviewState>(uncheckedPreviewState)) {
 		case Data::PreviewState::Cancelled:
@@ -1388,9 +1417,14 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
 			: Data::DraftKey::FromSerialized(keyValue);
 		if (key && !key.isCloud()) {
 			map.emplace(key, std::make_unique<Data::Draft>(
-				data,
-				messageId,
-				key.topicRootId(),
+				text,
+				FullReplyTo{
+					.messageId = FullMsgId(
+						PeerId(messageIdPeer),
+						MsgId(messageIdMsg)),
+					.quote = quote,
+					.topicRootId = key.topicRootId(),
+				},
 				MessageCursor(),
 				previewState));
 		}
@@ -1455,8 +1489,7 @@ void Account::readDraftsWithCursorsLegacy(
 			Data::DraftKey::Local(topicRootId),
 			std::make_unique<Data::Draft>(
 				msgData,
-				msgReplyTo,
-				topicRootId,
+				FullReplyTo{ FullMsgId(peerId, MsgId(msgReplyTo)) },
 				MessageCursor(),
 				(msgPreviewCancelled
 					? Data::PreviewState::Cancelled
@@ -1467,8 +1500,7 @@ void Account::readDraftsWithCursorsLegacy(
 			Data::DraftKey::LocalEdit(topicRootId),
 			std::make_unique<Data::Draft>(
 				editData,
-				editMsgId,
-				topicRootId,
+				FullReplyTo{ FullMsgId(peerId, editMsgId) },
 				MessageCursor(),
 				(editPreviewCancelled
 					? Data::PreviewState::Cancelled
diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h
index 3c1525ed7..ebc5ace38 100644
--- a/Telegram/SourceFiles/storage/storage_account.h
+++ b/Telegram/SourceFiles/storage/storage_account.h
@@ -51,7 +51,7 @@ using FileKey = quint64;
 enum class StartResult : uchar;
 
 struct MessageDraft {
-	MsgId msgId = 0;
+	FullReplyTo reply;
 	TextWithTags textWithTags;
 	Data::PreviewState previewState = Data::PreviewState::Allowed;
 };
diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp
index fd273c09f..c7cbb8e25 100644
--- a/Telegram/SourceFiles/support/support_helper.cpp
+++ b/Telegram/SourceFiles/support/support_helper.cpp
@@ -159,8 +159,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) {
 			+ QString::number(OccupationTag())
 			+ ";n:"
 			+ normalizedName },
-		MsgId(0), // replyTo
-		kTopicRootId,
+		FullReplyTo(),
 		MessageCursor(),
 		Data::PreviewState::Allowed
 	};
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index d00a49e71..b48e630e9 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -1060,12 +1060,14 @@ void Manager::notificationActivated(
 				const auto replyToId = (id.msgId > 0
 					&& !history->peer->isUser()
 					&& id.msgId != topicRootId)
-					? id.msgId
-					: 0;
+					? FullMsgId(history->peer->id, id.msgId)
+					: FullMsgId();
 				auto draft = std::make_unique<Data::Draft>(
 					reply,
-					replyToId,
-					topicRootId,
+					FullReplyTo{
+						.messageId = replyToId,
+						.topicRootId = topicRootId,
+					},
 					MessageCursor{
 						int(reply.text.size()),
 						int(reply.text.size()),
@@ -1150,7 +1152,7 @@ void Manager::notificationReplied(
 		? topicRootId
 		: MsgId(0);
 	message.action.replyTo = {
-		.msgId = replyToId,
+		.messageId = { replyToId ? history->peer->id : 0, replyToId },
 		.topicRootId = topic ? topic->rootId() : 0,
 	};
 	message.action.clearDraft = false;
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 13955e8db..e8ce10a78 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -112,7 +112,10 @@ void ShareBotGame(
 	}
 	histories.sendPreparedMessage(
 		history,
-		FullReplyTo{ .msgId = replyTo, .topicRootId = topicRootId },
+		FullReplyTo{
+			.messageId = { replyTo ? history->peer->id : 0, replyTo },
+			.topicRootId = topicRootId,
+		},
 		randomId,
 		Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
 			MTP_flags(flags),
@@ -1036,16 +1039,12 @@ void Filler::addCreatePoll() {
 		? SendMenu::Type::SilentOnly
 		: SendMenu::Type::Scheduled;
 	const auto flag = PollData::Flags();
-	const auto topicRootId = _request.rootId;
-	const auto replyToId = _request.currentReplyToId
-		? _request.currentReplyToId
-		: topicRootId;
+	const auto replyTo = _request.currentReplyTo;
 	auto callback = [=] {
 		PeerMenuCreatePoll(
 			controller,
 			peer,
-			replyToId,
-			topicRootId,
+			replyTo,
 			flag,
 			flag,
 			source,
@@ -1464,8 +1463,7 @@ void PeerMenuShareContactBox(
 void PeerMenuCreatePoll(
 		not_null<Window::SessionController*> controller,
 		not_null<PeerData*> peer,
-		MsgId replyToId,
-		MsgId topicRootId,
+		FullReplyTo replyTo,
 		PollData::Flags chosen,
 		PollData::Flags disabled,
 		Api::SendType sendType,
@@ -1490,10 +1488,12 @@ void PeerMenuCreatePoll(
 		auto action = Api::SendAction(
 			peer->owner().history(peer),
 			result.options);
-		action.clearDraft = false;
-		action.replyTo = { .msgId = replyToId, .topicRootId = topicRootId };
+		action.replyTo = replyTo;
+		const auto topicRootId = replyTo.topicRootId;
 		if (const auto local = action.history->localDraft(topicRootId)) {
 			action.clearDraft = local->textWithTags.text.isEmpty();
+		} else {
+			action.clearDraft = false;
 		}
 		const auto api = &peer->session().api();
 		api->polls().create(result.poll, action, crl::guard(weak, [=] {
@@ -1637,7 +1637,7 @@ void BlockSenderFromRepliesBox(
 	PeerMenuBlockUserBox(
 		box,
 		&controller->window(),
-		item->senderOriginal(),
+		item->originalSender(),
 		true,
 		Window::ClearReply{ id });
 }
diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h
index d5808fbf5..f5fff7f1f 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.h
+++ b/Telegram/SourceFiles/window/window_peer_menu.h
@@ -86,8 +86,7 @@ void PeerMenuAddChannelMembers(
 void PeerMenuCreatePoll(
 	not_null<Window::SessionController*> controller,
 	not_null<PeerData*> peer,
-	MsgId replyToId = 0,
-	MsgId topicRootId = 0,
+	FullReplyTo replyTo = FullReplyTo(),
 	PollData::Flags chosen = PollData::Flags(),
 	PollData::Flags disabled = PollData::Flags(),
 	Api::SendType sendType = Api::SendType::Normal,
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 7d223ee01..e17c9b28f 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -1526,8 +1526,7 @@ bool SessionController::switchInlineQuery(
 	};
 	auto draft = std::make_unique<Data::Draft>(
 		textWithTags,
-		to.currentReplyToId,
-		to.rootId,
+		to.currentReplyTo,
 		cursor,
 		Data::PreviewState::Allowed);
 
@@ -1539,11 +1538,12 @@ bool SessionController::switchInlineQuery(
 			std::make_shared<HistoryView::ScheduledMemento>(history),
 			params);
 	} else {
+		const auto topicRootId = to.currentReplyTo.topicRootId;
 		history->setLocalDraft(std::move(draft));
-		history->clearLocalEditDraft(to.rootId);
+		history->clearLocalEditDraft(topicRootId);
 		if (to.section == Section::Replies) {
 			const auto commentId = MsgId();
-			showRepliesForMessage(history, to.rootId, commentId, params);
+			showRepliesForMessage(history, topicRootId, commentId, params);
 		} else {
 			showPeerHistory(history->peer, params);
 		}
@@ -1560,7 +1560,7 @@ bool SessionController::switchInlineQuery(
 		.section = (thread->asTopic()
 			? Dialogs::EntryState::Section::Replies
 			: Dialogs::EntryState::Section::History),
-		.rootId = thread->topicRootId(),
+		.currentReplyTo = { .topicRootId = thread->topicRootId() },
 	};
 	return switchInlineQuery(entryState, bot, query);
 }