From 0b028b959bc396c72687d45d245c4e1dfe932536 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 11 Jun 2020 13:41:03 +0400
Subject: [PATCH] Move updates handling MainWidget -> Api::Updates.

---
 Telegram/CMakeLists.txt                       |    2 +
 Telegram/SourceFiles/api/api_updates.cpp      | 2030 +++++++++++++++++
 Telegram/SourceFiles/api/api_updates.h        |  172 ++
 Telegram/SourceFiles/apiwrap.cpp              |  359 +--
 Telegram/SourceFiles/apiwrap.h                |   22 +-
 Telegram/SourceFiles/core/application.cpp     |   38 +-
 Telegram/SourceFiles/core/application.h       |    3 +
 Telegram/SourceFiles/data/data_channel.cpp    |    2 +-
 Telegram/SourceFiles/data/data_pts_waiter.cpp |   31 +-
 Telegram/SourceFiles/data/data_pts_waiter.h   |   14 +-
 Telegram/SourceFiles/data/data_session.cpp    |   21 +
 Telegram/SourceFiles/data/data_session.h      |   13 +-
 .../SourceFiles/dialogs/dialogs_widget.cpp    |   29 +-
 Telegram/SourceFiles/dialogs/dialogs_widget.h |    7 +
 Telegram/SourceFiles/history/history.cpp      |    4 +-
 .../history/history_inner_widget.cpp          |    6 +
 .../SourceFiles/history/history_widget.cpp    |   43 +-
 Telegram/SourceFiles/history/history_widget.h |    7 +
 Telegram/SourceFiles/main/main_account.cpp    |   10 +-
 Telegram/SourceFiles/main/main_session.cpp    |   19 +
 Telegram/SourceFiles/main/main_session.h      |   21 +-
 Telegram/SourceFiles/mainwidget.cpp           | 1709 +-------------
 Telegram/SourceFiles/mainwidget.h             |  120 +-
 Telegram/SourceFiles/mainwindow.cpp           |   14 +-
 Telegram/SourceFiles/mtproto/sender.h         |    4 +-
 .../SourceFiles/settings/settings_codes.cpp   |    5 +-
 .../SourceFiles/storage/storage_account.cpp   |   29 +
 .../SourceFiles/storage/storage_account.h     |    1 +
 .../window/notifications_manager.cpp          |    7 +-
 .../window/window_session_controller.cpp      |    2 +
 30 files changed, 2526 insertions(+), 2218 deletions(-)
 create mode 100644 Telegram/SourceFiles/api/api_updates.cpp
 create mode 100644 Telegram/SourceFiles/api/api_updates.h

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index ea058fc40..d071feae1 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -167,6 +167,8 @@ PRIVATE
     api/api_single_message_search.h
     api/api_text_entities.cpp
     api/api_text_entities.h
+    api/api_updates.cpp
+    api/api_updates.h
     boxes/filters/edit_filter_box.cpp
     boxes/filters/edit_filter_box.h
     boxes/filters/edit_filter_chats_list.cpp
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
new file mode 100644
index 000000000..f66bac02b
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -0,0 +1,2030 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "api/api_updates.h"
+
+#include "api/api_text_entities.h"
+#include "main/main_session.h"
+#include "main/main_account.h"
+#include "mtproto/mtp_instance.h"
+#include "mtproto/dc_options.h"
+#include "data/stickers/data_stickers.h"
+#include "data/data_session.h"
+#include "data/data_user.h"
+#include "data/data_chat.h"
+#include "data/data_channel.h"
+#include "data/data_chat_filters.h"
+#include "data/data_cloud_themes.h"
+#include "data/data_drafts.h"
+#include "data/data_histories.h"
+#include "data/data_folder.h"
+#include "data/data_scheduled_messages.h"
+#include "lang/lang_cloud_manager.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "core/application.h"
+#include "storage/storage_account.h"
+#include "storage/storage_facade.h"
+#include "storage/storage_user_photos.h"
+#include "storage/storage_shared_media.h"
+#include "calls/calls_instance.h"
+#include "base/unixtime.h"
+#include "window/window_session_controller.h"
+#include "window/window_controller.h"
+#include "boxes/confirm_box.h"
+#include "apiwrap.h"
+#include "observer_peer.h"
+#include "app.h" // App::formatPhone
+#include "facades.h"
+
+namespace Api {
+namespace {
+
+constexpr auto kChannelGetDifferenceLimit = 100;
+
+// 1s wait after show channel history before sending getChannelDifference.
+constexpr auto kWaitForChannelGetDifference = crl::time(1000);
+
+// If nothing is received in 1 min we ping.
+constexpr auto kNoUpdatesTimeout = 60 * 1000;
+
+// If nothing is received in 1 min when was a sleepmode we ping.
+constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000);
+
+enum class DataIsLoadedResult {
+	NotLoaded = 0,
+	FromNotLoaded = 1,
+	MentionNotLoaded = 2,
+	Ok = 3,
+};
+
+bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) {
+	return qs(data.vtype()).startsWith(qstr("AUTH_KEY_DROP_"));
+}
+
+bool HasForceLogoutNotification(const MTPUpdates &updates) {
+	const auto checkUpdate = [](const MTPUpdate &update) {
+		if (update.type() != mtpc_updateServiceNotification) {
+			return false;
+		}
+		return IsForceLogoutNotification(
+			update.c_updateServiceNotification());
+	};
+	const auto checkVector = [&](const MTPVector<MTPUpdate> &list) {
+		for (const auto &update : list.v) {
+			if (checkUpdate(update)) {
+				return true;
+			}
+		}
+		return false;
+	};
+	switch (updates.type()) {
+	case mtpc_updates:
+		return checkVector(updates.c_updates().vupdates());
+	case mtpc_updatesCombined:
+		return checkVector(updates.c_updatesCombined().vupdates());
+	case mtpc_updateShort:
+		return checkUpdate(updates.c_updateShort().vupdate());
+	}
+	return false;
+}
+
+bool ForwardedInfoDataLoaded(
+		not_null<Main::Session*> session,
+		const MTPMessageFwdHeader &header) {
+	return header.match([&](const MTPDmessageFwdHeader &data) {
+		if (const auto channelId = data.vchannel_id()) {
+			if (!session->data().channelLoaded(channelId->v)) {
+				return false;
+			}
+			if (const auto fromId = data.vfrom_id()) {
+				const auto from = session->data().user(fromId->v);
+				// Minimal loaded is fine in this case.
+				if (from->loadedStatus == PeerData::NotLoaded) {
+					return false;
+				}
+			}
+		} else if (const auto fromId = data.vfrom_id()) {
+			// Fully loaded is required in this case.
+			if (!session->data().userLoaded(fromId->v)) {
+				return false;
+			}
+		}
+		return true;
+	});
+}
+
+bool MentionUsersLoaded(
+		not_null<Main::Session*> session,
+		const MTPVector<MTPMessageEntity> &entities) {
+	for (const auto &entity : entities.v) {
+		auto type = entity.type();
+		if (type == mtpc_messageEntityMentionName) {
+			if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id().v)) {
+				return false;
+			}
+		} else if (type == mtpc_inputMessageEntityMentionName) {
+			auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id();
+			if (inputUser.type() == mtpc_inputUser) {
+				if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id().v)) {
+					return false;
+				}
+			}
+		}
+	}
+	return true;
+}
+
+DataIsLoadedResult AllDataLoadedForMessage(
+		not_null<Main::Session*> session,
+		const MTPMessage &message) {
+	return message.match([&](const MTPDmessage &message) {
+		if (const auto fromId = message.vfrom_id()) {
+			if (!message.is_post()
+				&& !session->data().userLoaded(fromId->v)) {
+				return DataIsLoadedResult::FromNotLoaded;
+			}
+		}
+		if (const auto viaBotId = message.vvia_bot_id()) {
+			if (!session->data().userLoaded(viaBotId->v)) {
+				return DataIsLoadedResult::NotLoaded;
+			}
+		}
+		if (const auto fwd = message.vfwd_from()) {
+			if (!ForwardedInfoDataLoaded(session, *fwd)) {
+				return DataIsLoadedResult::NotLoaded;
+			}
+		}
+		if (const auto entities = message.ventities()) {
+			if (!MentionUsersLoaded(session, *entities)) {
+				return DataIsLoadedResult::MentionNotLoaded;
+			}
+		}
+		return DataIsLoadedResult::Ok;
+	}, [&](const MTPDmessageService &message) {
+		if (const auto fromId = message.vfrom_id()) {
+			if (!message.is_post()
+				&& !session->data().userLoaded(fromId->v)) {
+				return DataIsLoadedResult::FromNotLoaded;
+			}
+		}
+		return message.vaction().match(
+		[&](const MTPDmessageActionChatAddUser &action) {
+			for (const MTPint &userId : action.vusers().v) {
+				if (!session->data().userLoaded(userId.v)) {
+					return DataIsLoadedResult::NotLoaded;
+				}
+			}
+			return DataIsLoadedResult::Ok;
+		}, [&](const MTPDmessageActionChatJoinedByLink &action) {
+			if (!session->data().userLoaded(action.vinviter_id().v)) {
+				return DataIsLoadedResult::NotLoaded;
+			}
+			return DataIsLoadedResult::Ok;
+		}, [&](const MTPDmessageActionChatDeleteUser &action) {
+			if (!session->data().userLoaded(action.vuser_id().v)) {
+				return DataIsLoadedResult::NotLoaded;
+			}
+			return DataIsLoadedResult::Ok;
+		}, [](const auto &) {
+			return DataIsLoadedResult::Ok;
+		});
+	}, [](const MTPDmessageEmpty &message) {
+		return DataIsLoadedResult::Ok;
+	});
+}
+
+} // namespace
+
+Updates::Updates(not_null<Main::Session*> session)
+: _session(session)
+, _noUpdatesTimer([=] { sendPing(); })
+, _onlineTimer([=] { updateOnline(); })
+, _ptsWaiter(this)
+, _byPtsTimer([=] { getDifferenceByPts(); })
+, _bySeqTimer([=] { getDifference(); })
+, _byMinChannelTimer([=] { getDifference(); })
+, _failDifferenceTimer([=] { getDifferenceAfterFail(); })
+, _idleFinishTimer([=] { checkIdleFinish(); }) {
+	_ptsWaiter.setRequesting(true);
+
+	session->account().mtpUpdates(
+	) | rpl::start_with_next([=](const MTPUpdates &updates) {
+		mtpUpdateReceived(updates);
+	}, _lifetime);
+
+	session->account().mtpNewSessionCreated(
+	) | rpl::start_with_next([=] {
+		mtpNewSessionCreated();
+	}, _lifetime);
+
+	api().request(MTPupdates_GetState(
+	)).done([=](const MTPupdates_State &result) {
+	}).send();
+}
+
+Main::Session &Updates::session() const {
+	return *_session;
+}
+
+ApiWrap &Updates::api() const {
+	return _session->api();
+}
+
+void Updates::checkLastUpdate(bool afterSleep) {
+	const auto now = crl::now();
+	const auto skip = afterSleep
+		? kNoUpdatesAfterSleepTimeout
+		: kNoUpdatesTimeout;
+	if (_lastUpdateTime && now > _lastUpdateTime + skip) {
+		_lastUpdateTime = now;
+		sendPing();
+	}
+}
+
+void Updates::feedUpdateVector(
+		const MTPVector<MTPUpdate> &updates,
+		bool skipMessageIds) {
+	for (const auto &update : updates.v) {
+		if (skipMessageIds && update.type() == mtpc_updateMessageID) {
+			continue;
+		}
+		feedUpdate(update);
+	}
+	session().data().sendHistoryChangeNotifications();
+}
+
+void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
+	for (const auto &update : updates.v) {
+		if (update.type() == mtpc_updateMessageID) {
+			feedUpdate(update);
+		}
+	}
+}
+
+void Updates::setState(int32 pts, int32 date, int32 qts, int32 seq) {
+	if (pts) {
+		_ptsWaiter.init(pts);
+	}
+	if (_updatesDate < date && !_byMinChannelTimer.isActive()) {
+		_updatesDate = date;
+	}
+	if (qts && _updatesQts < qts) {
+		_updatesQts = qts;
+	}
+	if (seq && seq != _updatesSeq) {
+		_updatesSeq = seq;
+		if (_bySeqTimer.isActive()) {
+			_bySeqTimer.cancel();
+		}
+		while (!_bySeqUpdates.empty()) {
+			const auto s = _bySeqUpdates.front().first;
+			if (s <= seq + 1) {
+				const auto v = _bySeqUpdates.front().second;
+				_bySeqUpdates.erase(_bySeqUpdates.begin());
+				if (s == seq + 1) {
+					return applyUpdates(v);
+				}
+			} else {
+				if (!_bySeqTimer.isActive()) {
+					_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
+				}
+				break;
+			}
+		}
+	}
+}
+
+void Updates::channelDifferenceDone(
+		not_null<ChannelData*> channel,
+		const MTPupdates_ChannelDifference &difference) {
+	_channelFailDifferenceTimeout.remove(channel);
+
+	const auto timeout = difference.match([&](const auto &data) {
+		return data.vtimeout().value_or_empty();
+	});
+	const auto isFinal = difference.match([&](const auto &data) {
+		return data.is_final();
+	});
+	difference.match([&](const MTPDupdates_channelDifferenceEmpty &data) {
+		channel->ptsInit(data.vpts().v);
+	}, [&](const MTPDupdates_channelDifferenceTooLong &data) {
+		session().data().processUsers(data.vusers());
+		session().data().processChats(data.vchats());
+		const auto history = session().data().historyLoaded(channel->id);
+		if (history) {
+			history->setNotLoadedAtBottom();
+			requestChannelRangeDifference(history);
+		}
+		data.vdialog().match([&](const MTPDdialog &data) {
+			if (const auto pts = data.vpts()) {
+				channel->ptsInit(pts->v);
+			}
+		}, [&](const MTPDdialogFolder &) {
+		});
+		session().data().applyDialogs(
+			nullptr,
+			data.vmessages().v,
+			QVector<MTPDialog>(1, data.vdialog()));
+		session().data().channelDifferenceTooLong(channel);
+	}, [&](const MTPDupdates_channelDifference &data) {
+		feedChannelDifference(data);
+		channel->ptsInit(data.vpts().v);
+	});
+
+	channel->ptsSetRequesting(false);
+
+	if (!isFinal) {
+		MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
+		getChannelDifference(channel);
+	} else if (ranges::contains(
+			_activeChats,
+			channel,
+			[](const auto &pair) { return pair.second.peer; })) {
+		channel->ptsWaitingForShortPoll(timeout
+			? (timeout * crl::time(1000))
+			: kWaitForChannelGetDifference);
+	}
+}
+
+void Updates::feedChannelDifference(
+		const MTPDupdates_channelDifference &data) {
+	session().data().processUsers(data.vusers());
+	session().data().processChats(data.vchats());
+
+	_handlingChannelDifference = true;
+	feedMessageIds(data.vother_updates());
+	session().data().processMessages(
+		data.vnew_messages(),
+		NewMessageType::Unread);
+	feedUpdateVector(data.vother_updates(), true);
+	_handlingChannelDifference = false;
+}
+
+void Updates::channelDifferenceFail(
+		not_null<ChannelData*> channel,
+		const RPCError &error) {
+	LOG(("RPC Error in getChannelDifference: %1 %2: %3"
+		).arg(error.code()
+		).arg(error.type()
+		).arg(error.description()));
+	failDifferenceStartTimerFor(channel);
+}
+
+void Updates::stateDone(const MTPupdates_State &state) {
+	const auto &d = state.c_updates_state();
+	setState(d.vpts().v, d.vdate().v, d.vqts().v, d.vseq().v);
+
+	_lastUpdateTime = crl::now();
+	_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
+	_ptsWaiter.setRequesting(false);
+
+	session().api().requestDialogs();
+	updateOnline();
+}
+
+void Updates::differenceDone(const MTPupdates_Difference &result) {
+	_failDifferenceTimeout = 1;
+
+	switch (result.type()) {
+	case mtpc_updates_differenceEmpty: {
+		auto &d = result.c_updates_differenceEmpty();
+		setState(_ptsWaiter.current(), d.vdate().v, _updatesQts, d.vseq().v);
+
+		_lastUpdateTime = crl::now();
+		_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
+
+		_ptsWaiter.setRequesting(false);
+	} break;
+	case mtpc_updates_differenceSlice: {
+		auto &d = result.c_updates_differenceSlice();
+		feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates());
+
+		auto &s = d.vintermediate_state().c_updates_state();
+		setState(s.vpts().v, s.vdate().v, s.vqts().v, s.vseq().v);
+
+		_ptsWaiter.setRequesting(false);
+
+		MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
+		getDifference();
+	} break;
+	case mtpc_updates_difference: {
+		auto &d = result.c_updates_difference();
+		feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates());
+
+		stateDone(d.vstate());
+	} break;
+	case mtpc_updates_differenceTooLong: {
+		auto &d = result.c_updates_differenceTooLong();
+		LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!"));
+	} break;
+	};
+}
+
+bool Updates::whenGetDiffChanged(
+		ChannelData *channel,
+		int32 ms,
+		base::flat_map<not_null<ChannelData*>, crl::time> &whenMap,
+		crl::time &curTime) {
+	if (channel) {
+		if (ms <= 0) {
+			const auto i = whenMap.find(channel);
+			if (i != whenMap.cend()) {
+				whenMap.erase(i);
+			} else {
+				return false;
+			}
+		} else {
+			auto when = crl::now() + ms;
+			const auto i = whenMap.find(channel);
+			if (i != whenMap.cend()) {
+				if (i->second > when) {
+					i->second = when;
+				} else {
+					return false;
+				}
+			} else {
+				whenMap.emplace(channel, when);
+			}
+		}
+	} else {
+		if (ms <= 0) {
+			if (curTime) {
+				curTime = 0;
+			} else {
+				return false;
+			}
+		} else {
+			auto when = crl::now() + ms;
+			if (!curTime || curTime > when) {
+				curTime = when;
+			} else {
+				return false;
+			}
+		}
+	}
+	return true;
+}
+
+void Updates::ptsWaiterStartTimerFor(ChannelData *channel, crl::time ms) {
+	if (whenGetDiffChanged(channel, ms, _whenGetDiffByPts, _getDifferenceTimeByPts)) {
+		getDifferenceByPts();
+	}
+}
+
+void Updates::failDifferenceStartTimerFor(ChannelData *channel) {
+	auto &timeout = [&]() -> crl::time & {
+		if (!channel) {
+			return _failDifferenceTimeout;
+		}
+		const auto i = _channelFailDifferenceTimeout.find(channel);
+		return (i == _channelFailDifferenceTimeout.end())
+			? _channelFailDifferenceTimeout.emplace(channel, 1).first->second
+			: i->second;
+	}();
+	if (whenGetDiffChanged(channel, timeout * 1000, _whenGetDiffAfterFail, _getDifferenceTimeAfterFail)) {
+		getDifferenceAfterFail();
+	}
+	if (timeout < 64) timeout *= 2;
+}
+
+bool Updates::updateAndApply(
+		int32 pts,
+		int32 ptsCount,
+		const MTPUpdates &updates) {
+	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates);
+}
+
+bool Updates::updateAndApply(
+		int32 pts,
+		int32 ptsCount,
+		const MTPUpdate &update) {
+	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update);
+}
+
+bool Updates::updateAndApply(int32 pts, int32 ptsCount) {
+	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount);
+}
+
+void Updates::feedDifference(
+		const MTPVector<MTPUser> &users,
+		const MTPVector<MTPChat> &chats,
+		const MTPVector<MTPMessage> &msgs,
+		const MTPVector<MTPUpdate> &other) {
+	Core::App().checkAutoLock();
+	session().data().processUsers(users);
+	session().data().processChats(chats);
+	feedMessageIds(other);
+	session().data().processMessages(msgs, NewMessageType::Unread);
+	feedUpdateVector(other, true);
+}
+
+void Updates::differenceFail(const RPCError &error) {
+	LOG(("RPC Error in getDifference: %1 %2: %3"
+		).arg(error.code()
+		).arg(error.type()
+		).arg(error.description()));
+	failDifferenceStartTimerFor(nullptr);
+}
+
+void Updates::getDifferenceByPts() {
+	auto now = crl::now(), wait = crl::time(0);
+	if (_getDifferenceTimeByPts) {
+		if (_getDifferenceTimeByPts > now) {
+			wait = _getDifferenceTimeByPts - now;
+		} else {
+			getDifference();
+		}
+	}
+	for (auto i = _whenGetDiffByPts.begin(); i != _whenGetDiffByPts.cend();) {
+		if (i->second > now) {
+			wait = wait ? std::min(wait, i->second - now) : (i->second - now);
+			++i;
+		} else {
+			getChannelDifference(
+				i->first,
+				ChannelDifferenceRequest::PtsGapOrShortPoll);
+			i = _whenGetDiffByPts.erase(i);
+		}
+	}
+	if (wait) {
+		_byPtsTimer.callOnce(wait);
+	} else {
+		_byPtsTimer.cancel();
+	}
+}
+
+void Updates::getDifferenceAfterFail() {
+	auto now = crl::now(), wait = crl::time(0);
+	if (_getDifferenceTimeAfterFail) {
+		if (_getDifferenceTimeAfterFail > now) {
+			wait = _getDifferenceTimeAfterFail - now;
+		} else {
+			_ptsWaiter.setRequesting(false);
+			MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : ""));
+			getDifference();
+		}
+	}
+	for (auto i = _whenGetDiffAfterFail.begin(); i != _whenGetDiffAfterFail.cend();) {
+		if (i->second > now) {
+			wait = wait ? std::min(wait, i->second - now) : (i->second - now);
+			++i;
+		} else {
+			getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail);
+			i = _whenGetDiffAfterFail.erase(i);
+		}
+	}
+	if (wait) {
+		_failDifferenceTimer.callOnce(wait);
+	} else {
+		_failDifferenceTimer.cancel();
+	}
+}
+
+void Updates::getDifference() {
+	_getDifferenceTimeByPts = 0;
+
+	if (requestingDifference()) {
+		return;
+	}
+
+	_bySeqUpdates.clear();
+	_bySeqTimer.cancel();
+
+	_noUpdatesTimer.cancel();
+	_getDifferenceTimeAfterFail = 0;
+
+	_ptsWaiter.setRequesting(true);
+
+	api().request(MTPupdates_GetDifference(
+		MTP_flags(0),
+		MTP_int(_ptsWaiter.current()),
+		MTPint(),
+		MTP_int(_updatesDate),
+		MTP_int(_updatesQts)
+	)).done([=](const MTPupdates_Difference &result) {
+		differenceDone(result);
+	}).fail([=](const RPCError &error) {
+		differenceFail(error);
+	}).send();
+}
+
+void Updates::getChannelDifference(
+		not_null<ChannelData*> channel,
+		ChannelDifferenceRequest from) {
+	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
+		_whenGetDiffByPts.remove(channel);
+	}
+
+	if (!channel->ptsInited() || channel->ptsRequesting()) return;
+
+	if (from != ChannelDifferenceRequest::AfterFail) {
+		_whenGetDiffAfterFail.remove(channel);
+	}
+
+	channel->ptsSetRequesting(true);
+
+	auto filter = MTP_channelMessagesFilterEmpty();
+	auto flags = MTPupdates_GetChannelDifference::Flag::f_force | 0;
+	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
+		if (!channel->ptsWaitingForSkipped()) {
+			flags = 0; // No force flag when requesting for short poll.
+		}
+	}
+	api().request(MTPupdates_GetChannelDifference(
+		MTP_flags(flags),
+		channel->inputChannel,
+		filter,
+		MTP_int(channel->pts()),
+		MTP_int(kChannelGetDifferenceLimit)
+	)).done([=](const MTPupdates_ChannelDifference &result) {
+		channelDifferenceDone(channel, result);
+	}).fail([=](const RPCError &error) {
+		channelDifferenceFail(channel, error);
+	}).send();
+}
+
+void Updates::sendPing() {
+	api().instance()->ping();
+}
+
+void Updates::addActiveChat(rpl::producer<PeerData*> chat) {
+	const auto key = _activeChats.empty() ? 0 : _activeChats.back().first + 1;
+	std::move(
+		chat
+	) | rpl::start_with_next_done([=](PeerData *peer) {
+		_activeChats[key].peer = peer;
+		if (const auto channel = peer ? peer->asChannel() : nullptr) {
+			channel->ptsWaitingForShortPoll(
+				kWaitForChannelGetDifference);
+		}
+	}, [=] {
+		_activeChats.erase(key);
+	}, _activeChats[key].lifetime);
+}
+
+void Updates::requestChannelRangeDifference(not_null<History*> history) {
+	Expects(history->isChannel());
+
+	const auto channel = history->peer->asChannel();
+	if (const auto requestId = _rangeDifferenceRequests.take(channel)) {
+		api().request(*requestId).cancel();
+	}
+	const auto range = history->rangeForDifferenceRequest();
+	if (!(range.from < range.till) || !channel->pts()) {
+		return;
+	}
+
+	MTP_LOG(0, ("getChannelDifference { good - "
+		"after channelDifferenceTooLong was received, "
+		"validating history part }%1").arg(cTestMode() ? " TESTMODE" : ""));
+	channelRangeDifferenceSend(channel, range, channel->pts());
+}
+
+void Updates::channelRangeDifferenceSend(
+		not_null<ChannelData*> channel,
+		MsgRange range,
+		int32 pts) {
+	Expects(range.from < range.till);
+
+	const auto limit = range.till - range.from;
+	const auto filter = MTP_channelMessagesFilter(
+		MTP_flags(0),
+		MTP_vector<MTPMessageRange>(1, MTP_messageRange(
+			MTP_int(range.from),
+			MTP_int(range.till - 1))));
+	const auto requestId = api().request(MTPupdates_GetChannelDifference(
+		MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force),
+		channel->inputChannel,
+		filter,
+		MTP_int(pts),
+		MTP_int(limit)
+	)).done([=](const MTPupdates_ChannelDifference &result) {
+		_rangeDifferenceRequests.remove(channel);
+		channelRangeDifferenceDone(channel, range, result);
+	}).fail([=](const RPCError &error) {
+		_rangeDifferenceRequests.remove(channel);
+	}).send();
+	_rangeDifferenceRequests.emplace(channel, requestId);
+}
+
+void Updates::channelRangeDifferenceDone(
+		not_null<ChannelData*> channel,
+		MsgRange range,
+		const MTPupdates_ChannelDifference &result) {
+	auto nextRequestPts = int32(0);
+	auto isFinal = true;
+
+	switch (result.type()) {
+	case mtpc_updates_channelDifferenceEmpty: {
+		const auto &d = result.c_updates_channelDifferenceEmpty();
+		nextRequestPts = d.vpts().v;
+		isFinal = d.is_final();
+	} break;
+
+	case mtpc_updates_channelDifferenceTooLong: {
+		const auto &d = result.c_updates_channelDifferenceTooLong();
+
+		_session->data().processUsers(d.vusers());
+		_session->data().processChats(d.vchats());
+
+		nextRequestPts = d.vdialog().match([&](const MTPDdialog &data) {
+			return data.vpts().value_or_empty();
+		}, [&](const MTPDdialogFolder &data) {
+			return 0;
+		});
+		isFinal = d.is_final();
+	} break;
+
+	case mtpc_updates_channelDifference: {
+		const auto &d = result.c_updates_channelDifference();
+
+		feedChannelDifference(d);
+
+		nextRequestPts = d.vpts().v;
+		isFinal = d.is_final();
+	} break;
+	}
+
+	if (!isFinal && nextRequestPts) {
+		MTP_LOG(0, ("getChannelDifference { "
+			"good - after not final channelDifference was received, "
+			"validating history part }%1"
+			).arg(cTestMode() ? " TESTMODE" : ""));
+		channelRangeDifferenceSend(channel, range, nextRequestPts);
+	}
+}
+
+void Updates::mtpNewSessionCreated() {
+	Core::App().checkAutoLock();
+	_updatesSeq = 0;
+	MTP_LOG(0, ("getDifference { after new_session_created }%1"
+		).arg(cTestMode() ? " TESTMODE" : ""));
+	getDifference();
+}
+
+void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
+	Core::App().checkAutoLock();
+	_lastUpdateTime = crl::now();
+	_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
+	if (!requestingDifference()
+		|| HasForceLogoutNotification(updates)) {
+		applyUpdates(updates);
+	}
+}
+
+void Updates::updateOnline() {
+	updateOnline(false);
+}
+
+bool Updates::isIdle() const {
+	return _isIdle;
+}
+
+void Updates::updateOnline(bool gotOtherOffline) {
+	crl::on_main(&session(), [] { Core::App().checkAutoLock(); });
+
+	bool isOnline = Core::App().hasActiveWindow(&session());
+	int updateIn = Global::OnlineUpdatePeriod();
+	Assert(updateIn >= 0);
+	if (isOnline) {
+		const auto idle = crl::now() - Core::App().lastNonIdleTime();
+		if (idle >= Global::OfflineIdleTimeout()) {
+			isOnline = false;
+			if (!_isIdle) {
+				_isIdle = true;
+				_idleFinishTimer.callOnce(900);
+			}
+		} else {
+			updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle));
+			Assert(updateIn >= 0);
+		}
+	}
+	auto ms = crl::now();
+	if (isOnline != _lastWasOnline
+		|| (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms)
+		|| (isOnline && gotOtherOffline)) {
+		api().request(base::take(_onlineRequest)).cancel();
+
+		_lastWasOnline = isOnline;
+		_lastSetOnline = ms;
+		if (!App::quitting()) {
+			_onlineRequest = api().request(MTPaccount_UpdateStatus(
+				MTP_bool(!isOnline)
+			)).send();
+		} else {
+			_onlineRequest = api().request(MTPaccount_UpdateStatus(
+				MTP_bool(!isOnline)
+			)).done([=](const MTPBool &result) {
+				Core::App().quitPreventFinished();
+			}).fail([=](const RPCError &error) {
+				Core::App().quitPreventFinished();
+			}).send();
+		}
+
+		const auto self = session().user();
+		self->onlineTill = base::unixtime::now() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1);
+		Notify::peerUpdatedDelayed(
+			self,
+			Notify::PeerUpdate::Flag::UserOnlineChanged);
+		if (!isOnline) { // Went offline, so we need to save message draft to the cloud.
+			api().saveCurrentDraftToCloud();
+		}
+
+		_lastSetOnline = ms;
+	} else if (isOnline) {
+		updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms));
+		Assert(updateIn >= 0);
+	}
+	_onlineTimer.callOnce(updateIn);
+}
+
+void Updates::checkIdleFinish() {
+	if (crl::now() - Core::App().lastNonIdleTime()
+		< Global::OfflineIdleTimeout()) {
+		_idleFinishTimer.cancel();
+		_isIdle = false;
+		updateOnline();
+		App::wnd()->checkHistoryActivation();
+	} else {
+		_idleFinishTimer.callOnce(900);
+	}
+}
+
+bool Updates::lastWasOnline() const {
+	return _lastWasOnline;
+}
+
+crl::time Updates::lastSetOnline() const {
+	return _lastSetOnline;
+}
+
+bool Updates::isQuitPrevent() {
+	if (!_lastWasOnline) {
+		return false;
+	}
+	LOG(("Api::Updates prevents quit, sending offline status..."));
+	updateOnline();
+	return true;
+}
+
+void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
+	switch (updates.type()) {
+	case mtpc_updateShortMessage: {
+		const auto &d = updates.c_updateShortMessage();
+		const auto flags = mtpCastFlags(d.vflags().v)
+			| MTPDmessage::Flag::f_from_id;
+		const auto peerUserId = d.is_out()
+			? d.vuser_id()
+			: MTP_int(_session->userId());
+		const auto fwd = d.vfwd_from();
+		_session->data().addNewMessage(
+			MTP_message(
+				MTP_flags(flags),
+				d.vid(),
+				d.is_out() ? MTP_int(_session->userId()) : d.vuser_id(),
+				MTP_peerUser(peerUserId),
+				fwd ? (*fwd) : MTPMessageFwdHeader(),
+				MTP_int(d.vvia_bot_id().value_or_empty()),
+				MTP_int(d.vreply_to_msg_id().value_or_empty()),
+				d.vdate(),
+				d.vmessage(),
+				MTP_messageMediaEmpty(),
+				MTPReplyMarkup(),
+				MTP_vector<MTPMessageEntity>(d.ventities().value_or_empty()),
+				MTPint(),
+				MTPint(),
+				MTPstring(),
+				MTPlong(),
+				//MTPMessageReactions(),
+				MTPVector<MTPRestrictionReason>()),
+			MTPDmessage_ClientFlags(),
+			NewMessageType::Unread);
+	} break;
+
+	case mtpc_updateShortChatMessage: {
+		const auto &d = updates.c_updateShortChatMessage();
+		const auto flags = mtpCastFlags(d.vflags().v) | MTPDmessage::Flag::f_from_id;
+		const auto fwd = d.vfwd_from();
+		_session->data().addNewMessage(
+			MTP_message(
+				MTP_flags(flags),
+				d.vid(),
+				d.vfrom_id(),
+				MTP_peerChat(d.vchat_id()),
+				fwd ? (*fwd) : MTPMessageFwdHeader(),
+				MTP_int(d.vvia_bot_id().value_or_empty()),
+				MTP_int(d.vreply_to_msg_id().value_or_empty()),
+				d.vdate(),
+				d.vmessage(),
+				MTP_messageMediaEmpty(),
+				MTPReplyMarkup(),
+				MTP_vector<MTPMessageEntity>(d.ventities().value_or_empty()),
+				MTPint(),
+				MTPint(),
+				MTPstring(),
+				MTPlong(),
+				//MTPMessageReactions(),
+				MTPVector<MTPRestrictionReason>()),
+			MTPDmessage_ClientFlags(),
+			NewMessageType::Unread);
+	} break;
+
+	case mtpc_updateShortSentMessage: {
+		auto &d = updates.c_updateShortSentMessage();
+		Q_UNUSED(d); // Sent message data was applied anyway.
+	} break;
+
+	default: Unexpected("Type in applyUpdatesNoPtsCheck()");
+	}
+}
+
+void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
+	switch (update.type()) {
+	case mtpc_updateNewMessage: {
+		auto &d = update.c_updateNewMessage();
+		auto needToAdd = true;
+		if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
+			const auto &data = d.vmessage().c_message();
+			if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks
+				LOG(("Skipping message, because it is already in blocks!"));
+				needToAdd = false;
+			}
+			if (needToAdd && !data.is_from_scheduled()) {
+				// If we still need to add a new message,
+				// we should first check if this message is in
+				// the list of scheduled messages.
+				// This is necessary to correctly update the file reference.
+				// Note that when a message is scheduled until online
+				// while the recipient is already online, the server sends
+				// an ordinary new message with skipped "from_scheduled" flag.
+				_session->data().scheduledMessages().checkEntitiesAndUpdate(
+					data);
+			}
+		}
+		if (needToAdd) {
+			_session->data().addNewMessage(
+				d.vmessage(),
+				MTPDmessage_ClientFlags(),
+				NewMessageType::Unread);
+		}
+	} break;
+
+	case mtpc_updateReadMessagesContents: {
+		const auto &d = update.c_updateReadMessagesContents();
+		auto possiblyReadMentions = base::flat_set<MsgId>();
+		for (const auto &msgId : d.vmessages().v) {
+			if (const auto item = _session->data().message(NoChannel, msgId.v)) {
+				if (item->isUnreadMedia() || item->isUnreadMention()) {
+					item->markMediaRead();
+					_session->data().requestItemRepaint(item);
+
+					if (item->out()
+						&& item->history()->peer->isUser()
+						&& !requestingDifference()) {
+						item->history()->peer->asUser()->madeAction(base::unixtime::now());
+					}
+				}
+			} else {
+				// Perhaps it was an unread mention!
+				possiblyReadMentions.insert(msgId.v);
+			}
+		}
+		session().api().checkForUnreadMentions(possiblyReadMentions);
+	} break;
+
+	case mtpc_updateReadHistoryInbox: {
+		const auto &d = update.c_updateReadHistoryInbox();
+		const auto peer = peerFromMTP(d.vpeer());
+		if (const auto history = _session->data().historyLoaded(peer)) {
+			const auto folderId = d.vfolder_id().value_or_empty();
+			history->applyInboxReadUpdate(
+				folderId,
+				d.vmax_id().v,
+				d.vstill_unread_count().v);
+		}
+	} break;
+
+	case mtpc_updateReadHistoryOutbox: {
+		const auto &d = update.c_updateReadHistoryOutbox();
+		const auto peer = peerFromMTP(d.vpeer());
+		if (const auto history = _session->data().historyLoaded(peer)) {
+			history->outboxRead(d.vmax_id().v);
+			if (!requestingDifference()) {
+				if (const auto user = history->peer->asUser()) {
+					user->madeAction(base::unixtime::now());
+				}
+			}
+		}
+	} break;
+
+	case mtpc_updateWebPage: {
+		auto &d = update.c_updateWebPage();
+		Q_UNUSED(d); // Web page was updated anyway.
+	} break;
+
+	case mtpc_updateFolderPeers: {
+		const auto &data = update.c_updateFolderPeers();
+		auto &owner = _session->data();
+		for (const auto &peer : data.vfolder_peers().v) {
+			peer.match([&](const MTPDfolderPeer &data) {
+				const auto peerId = peerFromMTP(data.vpeer());
+				if (const auto history = owner.historyLoaded(peerId)) {
+					if (const auto folderId = data.vfolder_id().v) {
+						history->setFolder(owner.folder(folderId));
+					} else {
+						history->clearFolder();
+					}
+				}
+			});
+		}
+	} break;
+
+	case mtpc_updateDeleteMessages: {
+		auto &d = update.c_updateDeleteMessages();
+		_session->data().processMessagesDeleted(NoChannel, d.vmessages().v);
+	} break;
+
+	case mtpc_updateNewChannelMessage: {
+		auto &d = update.c_updateNewChannelMessage();
+		auto needToAdd = true;
+		if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
+			if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks
+				LOG(("Skipping message, because it is already in blocks!"));
+				needToAdd = false;
+			}
+		}
+		if (needToAdd) {
+			_session->data().addNewMessage(
+				d.vmessage(),
+				MTPDmessage_ClientFlags(),
+				NewMessageType::Unread);
+		}
+	} break;
+
+	case mtpc_updateEditChannelMessage: {
+		auto &d = update.c_updateEditChannelMessage();
+		_session->data().updateEditedMessage(d.vmessage());
+	} break;
+
+	case mtpc_updateEditMessage: {
+		auto &d = update.c_updateEditMessage();
+		_session->data().updateEditedMessage(d.vmessage());
+	} break;
+
+	case mtpc_updateChannelWebPage: {
+		auto &d = update.c_updateChannelWebPage();
+		Q_UNUSED(d); // Web page was updated anyway.
+	} break;
+
+	case mtpc_updateDeleteChannelMessages: {
+		auto &d = update.c_updateDeleteChannelMessages();
+		_session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v);
+	} break;
+
+	default: Unexpected("Type in applyUpdateNoPtsCheck()");
+	}
+}
+
+void Updates::applyUpdates(
+		const MTPUpdates &updates,
+		uint64 sentMessageRandomId) {
+	const auto randomId = sentMessageRandomId;
+
+	switch (updates.type()) {
+	case mtpc_updates: {
+		auto &d = updates.c_updates();
+		if (d.vseq().v) {
+			if (d.vseq().v <= _updatesSeq) {
+				return;
+			}
+			if (d.vseq().v > _updatesSeq + 1) {
+				_bySeqUpdates.emplace(d.vseq().v, updates);
+				_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
+				return;
+			}
+		}
+
+		session().data().processUsers(d.vusers());
+		session().data().processChats(d.vchats());
+		feedUpdateVector(d.vupdates());
+
+		setState(0, d.vdate().v, _updatesQts, d.vseq().v);
+	} break;
+
+	case mtpc_updatesCombined: {
+		auto &d = updates.c_updatesCombined();
+		if (d.vseq_start().v) {
+			if (d.vseq_start().v <= _updatesSeq) {
+				return;
+			}
+			if (d.vseq_start().v > _updatesSeq + 1) {
+				_bySeqUpdates.emplace(d.vseq_start().v, updates);
+				_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
+				return;
+			}
+		}
+
+		session().data().processUsers(d.vusers());
+		session().data().processChats(d.vchats());
+		feedUpdateVector(d.vupdates());
+
+		setState(0, d.vdate().v, _updatesQts, d.vseq().v);
+	} break;
+
+	case mtpc_updateShort: {
+		auto &d = updates.c_updateShort();
+		feedUpdate(d.vupdate());
+
+		setState(0, d.vdate().v, _updatesQts, _updatesSeq);
+	} break;
+
+	case mtpc_updateShortMessage: {
+		auto &d = updates.c_updateShortMessage();
+		const auto viaBotId = d.vvia_bot_id();
+		const auto entities = d.ventities();
+		const auto fwd = d.vfwd_from();
+		if (!session().data().userLoaded(d.vuser_id().v)
+			|| (viaBotId && !session().data().userLoaded(viaBotId->v))
+			|| (entities && !MentionUsersLoaded(&session(), *entities))
+			|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
+			MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
+			return getDifference();
+		}
+		if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
+			// Update date as well.
+			setState(0, d.vdate().v, _updatesQts, _updatesSeq);
+		}
+	} break;
+
+	case mtpc_updateShortChatMessage: {
+		auto &d = updates.c_updateShortChatMessage();
+		const auto noFrom = !session().data().userLoaded(d.vfrom_id().v);
+		const auto chat = session().data().chatLoaded(d.vchat_id().v);
+		const auto viaBotId = d.vvia_bot_id();
+		const auto entities = d.ventities();
+		const auto fwd = d.vfwd_from();
+		if (!chat
+			|| noFrom
+			|| (viaBotId && !session().data().userLoaded(viaBotId->v))
+			|| (entities && !MentionUsersLoaded(&session(), *entities))
+			|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
+			MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
+			if (chat && noFrom) {
+				session().api().requestFullPeer(chat);
+			}
+			return getDifference();
+		}
+		if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
+			// Update date as well.
+			setState(0, d.vdate().v, _updatesQts, _updatesSeq);
+		}
+	} break;
+
+	case mtpc_updateShortSentMessage: {
+		auto &d = updates.c_updateShortSentMessage();
+		if (!IsServerMsgId(d.vid().v)) {
+			LOG(("API Error: Bad msgId got from server: %1").arg(d.vid().v));
+		} else if (randomId) {
+			auto &owner = session().data();
+			const auto sent = owner.messageSentData(randomId);
+			const auto lookupMessage = [&] {
+				return sent.peerId
+					? owner.message(peerToChannel(sent.peerId), d.vid().v)
+					: nullptr;
+			};
+			if (const auto id = owner.messageIdByRandomId(randomId)) {
+				const auto local = owner.message(id);
+				if (local && local->isScheduled()) {
+					owner.scheduledMessages().sendNowSimpleMessage(d, local);
+				}
+			}
+			const auto wasAlready = (lookupMessage() != nullptr);
+			feedUpdate(MTP_updateMessageID(d.vid(), MTP_long(randomId))); // ignore real date
+			if (const auto item = lookupMessage()) {
+				const auto list = d.ventities();
+				if (list && !MentionUsersLoaded(&session(), *list)) {
+					session().api().requestMessageData(
+						item->history()->peer->asChannel(),
+						item->id,
+						ApiWrap::RequestMessageDataCallback());
+				}
+				item->updateSentContent({
+					sent.text,
+					Api::EntitiesFromMTP(&session(), list.value_or_empty())
+				}, d.vmedia());
+				item->contributeToSlowmode(d.vdate().v);
+				if (!wasAlready) {
+					item->indexAsNewItem();
+				}
+			}
+		}
+
+		if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
+			// Update date as well.
+			setState(0, d.vdate().v, _updatesQts, _updatesSeq);
+		}
+	} break;
+
+	case mtpc_updatesTooLong: {
+		MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : ""));
+		return getDifference();
+	} break;
+	}
+	session().data().sendHistoryChangeNotifications();
+}
+
+void Updates::feedUpdate(const MTPUpdate &update) {
+	switch (update.type()) {
+
+	// New messages.
+	case mtpc_updateNewMessage: {
+		auto &d = update.c_updateNewMessage();
+
+		const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage());
+		if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) {
+			MTP_LOG(0, ("getDifference { good - "
+				"after not all data loaded in updateNewMessage }%1"
+				).arg(cTestMode() ? " TESTMODE" : ""));
+
+			// This can be if this update was created by grouping
+			// some short message update into an updates vector.
+			return getDifference();
+		}
+
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateNewChannelMessage: {
+		auto &d = update.c_updateNewChannelMessage();
+		auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage())));
+		const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage());
+		if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) {
+			MTP_LOG(0, ("getDifference { good - "
+				"after not all data loaded in updateNewChannelMessage }%1"
+				).arg(cTestMode() ? " TESTMODE" : ""));
+
+			// Request last active supergroup participants if the 'from' user was not loaded yet.
+			// This will optimize similar getDifference() calls for almost all next messages.
+			if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) {
+				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) {
+					session().api().requestLastParticipants(channel);
+				}
+			}
+
+			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
+				_byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
+			}
+			return;
+		}
+		if (channel && !_handlingChannelDifference) {
+			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
+				return;
+			}
+			channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
+		} else {
+			applyUpdateNoPtsCheck(update);
+		}
+	} break;
+
+	case mtpc_updateMessageID: {
+		const auto &d = update.c_updateMessageID();
+		const auto randomId = d.vrandom_id().v;
+		if (const auto id = session().data().messageIdByRandomId(randomId)) {
+			const auto newId = d.vid().v;
+			if (const auto local = session().data().message(id)) {
+				if (local->isScheduled()) {
+					session().data().scheduledMessages().apply(d, local);
+				} else {
+					const auto channel = id.channel;
+					const auto existing = session().data().message(
+						channel,
+						newId);
+					if (existing && !local->mainView()) {
+						const auto history = local->history();
+						local->destroy();
+						history->requestChatListMessage();
+					} else {
+						if (existing) {
+							existing->destroy();
+						}
+						local->setRealId(d.vid().v);
+					}
+				}
+			}
+			session().data().unregisterMessageRandomId(randomId);
+		}
+		session().data().unregisterMessageSentData(randomId);
+	} break;
+
+	// Message contents being read.
+	case mtpc_updateReadMessagesContents: {
+		auto &d = update.c_updateReadMessagesContents();
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateChannelReadMessagesContents: {
+		auto &d = update.c_updateChannelReadMessagesContents();
+		auto channel = session().data().channelLoaded(d.vchannel_id().v);
+		if (!channel) {
+			if (!_byMinChannelTimer.isActive()) {
+				// getDifference after timeout.
+				_byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
+			}
+			return;
+		}
+		auto possiblyReadMentions = base::flat_set<MsgId>();
+		for_const (auto &msgId, d.vmessages().v) {
+			if (auto item = session().data().message(channel, msgId.v)) {
+				if (item->isUnreadMedia() || item->isUnreadMention()) {
+					item->markMediaRead();
+					session().data().requestItemRepaint(item);
+				}
+			} else {
+				// Perhaps it was an unread mention!
+				possiblyReadMentions.insert(msgId.v);
+			}
+		}
+		session().api().checkForUnreadMentions(possiblyReadMentions, channel);
+	} break;
+
+	// Edited messages.
+	case mtpc_updateEditMessage: {
+		auto &d = update.c_updateEditMessage();
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateEditChannelMessage: {
+		auto &d = update.c_updateEditChannelMessage();
+		auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage())));
+
+		if (channel && !_handlingChannelDifference) {
+			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
+				return;
+			} else {
+				channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
+			}
+		} else {
+			applyUpdateNoPtsCheck(update);
+		}
+	} break;
+
+	// Messages being read.
+	case mtpc_updateReadHistoryInbox: {
+		auto &d = update.c_updateReadHistoryInbox();
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateReadHistoryOutbox: {
+		auto &d = update.c_updateReadHistoryOutbox();
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateReadChannelInbox: {
+		const auto &d = update.c_updateReadChannelInbox();
+		const auto peer = peerFromChannel(d.vchannel_id().v);
+		if (const auto history = session().data().historyLoaded(peer)) {
+			history->applyInboxReadUpdate(
+				d.vfolder_id().value_or_empty(),
+				d.vmax_id().v,
+				d.vstill_unread_count().v,
+				d.vpts().v);
+		}
+	} break;
+
+	case mtpc_updateReadChannelOutbox: {
+		const auto &d = update.c_updateReadChannelOutbox();
+		const auto peer = peerFromChannel(d.vchannel_id().v);
+		if (const auto history = session().data().historyLoaded(peer)) {
+			history->outboxRead(d.vmax_id().v);
+			if (!requestingDifference()) {
+				if (const auto user = history->peer->asUser()) {
+					user->madeAction(base::unixtime::now());
+				}
+			}
+		}
+	} break;
+
+	//case mtpc_updateReadFeed: { // #feed
+	//	const auto &d = update.c_updateReadFeed();
+	//	const auto feedId = d.vfeed_id().v;
+	//	if (const auto feed = session().data().feedLoaded(feedId)) {
+	//		feed->setUnreadPosition(
+	//			Data::FeedPositionFromMTP(d.vmax_position()));
+	//		if (d.vunread_count() && d.vunread_muted_count()) {
+	//			feed->setUnreadCounts(
+	//				d.vunread_count()->v,
+	//				d.vunread_muted_count()->v);
+	//		} else {
+	//			session().data().histories().requestDialogEntry(feed);
+	//		}
+	//	}
+	//} break;
+
+	case mtpc_updateDialogUnreadMark: {
+		const auto &data = update.c_updateDialogUnreadMark();
+		data.vpeer().match(
+		[&](const MTPDdialogPeer &dialog) {
+			const auto id = peerFromMTP(dialog.vpeer());
+			if (const auto history = session().data().historyLoaded(id)) {
+				history->setUnreadMark(data.is_unread());
+			}
+		}, [&](const MTPDdialogPeerFolder &dialog) {
+			const auto id = dialog.vfolder_id().v; // #TODO archive
+			//if (const auto folder = session().data().folderLoaded(id)) {
+			//	folder->setUnreadMark(data.is_unread());
+			//}
+		});
+	} break;
+
+	case mtpc_updateFolderPeers: {
+		const auto &data = update.c_updateFolderPeers();
+
+		updateAndApply(data.vpts().v, data.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateDialogFilter:
+	case mtpc_updateDialogFilterOrder:
+	case mtpc_updateDialogFilters: {
+		session().data().chatsFilters().apply(update);
+	} break;
+
+	// Deleted messages.
+	case mtpc_updateDeleteMessages: {
+		auto &d = update.c_updateDeleteMessages();
+
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateDeleteChannelMessages: {
+		auto &d = update.c_updateDeleteChannelMessages();
+		auto channel = session().data().channelLoaded(d.vchannel_id().v);
+
+		if (channel && !_handlingChannelDifference) {
+			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
+				return;
+			}
+			channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
+		} else {
+			applyUpdateNoPtsCheck(update);
+		}
+	} break;
+
+	case mtpc_updateNewScheduledMessage: {
+		const auto &d = update.c_updateNewScheduledMessage();
+		session().data().scheduledMessages().apply(d);
+	} break;
+
+	case mtpc_updateDeleteScheduledMessages: {
+		const auto &d = update.c_updateDeleteScheduledMessages();
+		session().data().scheduledMessages().apply(d);
+	} break;
+
+	case mtpc_updateWebPage: {
+		auto &d = update.c_updateWebPage();
+
+		// Update web page anyway.
+		session().data().processWebpage(d.vwebpage());
+		session().data().sendWebPageGamePollNotifications();
+
+		updateAndApply(d.vpts().v, d.vpts_count().v, update);
+	} break;
+
+	case mtpc_updateChannelWebPage: {
+		auto &d = update.c_updateChannelWebPage();
+
+		// Update web page anyway.
+		session().data().processWebpage(d.vwebpage());
+		session().data().sendWebPageGamePollNotifications();
+
+		auto channel = session().data().channelLoaded(d.vchannel_id().v);
+		if (channel && !_handlingChannelDifference) {
+			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
+				return;
+			} else {
+				channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
+			}
+		} else {
+			applyUpdateNoPtsCheck(update);
+		}
+	} break;
+
+	case mtpc_updateMessagePoll: {
+		session().data().applyUpdate(update.c_updateMessagePoll());
+	} break;
+
+	case mtpc_updateUserTyping: {
+		auto &d = update.c_updateUserTyping();
+		const auto userId = peerFromUser(d.vuser_id());
+		const auto history = session().data().historyLoaded(userId);
+		const auto user = session().data().userLoaded(d.vuser_id().v);
+		if (history && user) {
+			const auto when = requestingDifference() ? 0 : base::unixtime::now();
+			session().data().registerSendAction(history, user, d.vaction(), when);
+		}
+	} break;
+
+	case mtpc_updateChatUserTyping: {
+		auto &d = update.c_updateChatUserTyping();
+		const auto history = [&]() -> History* {
+			if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) {
+				return session().data().historyLoaded(chat->id);
+			} else if (const auto channel = session().data().channelLoaded(d.vchat_id().v)) {
+				return session().data().historyLoaded(channel->id);
+			}
+			return nullptr;
+		}();
+		const auto user = (d.vuser_id().v == session().userId())
+			? nullptr
+			: session().data().userLoaded(d.vuser_id().v);
+		if (history && user) {
+			const auto when = requestingDifference() ? 0 : base::unixtime::now();
+			session().data().registerSendAction(history, user, d.vaction(), when);
+		}
+	} break;
+
+	case mtpc_updateChatParticipants: {
+		session().data().applyUpdate(update.c_updateChatParticipants());
+	} break;
+
+	case mtpc_updateChatParticipantAdd: {
+		session().data().applyUpdate(update.c_updateChatParticipantAdd());
+	} break;
+
+	case mtpc_updateChatParticipantDelete: {
+		session().data().applyUpdate(update.c_updateChatParticipantDelete());
+	} break;
+
+	case mtpc_updateChatParticipantAdmin: {
+		session().data().applyUpdate(update.c_updateChatParticipantAdmin());
+	} break;
+
+	case mtpc_updateChatDefaultBannedRights: {
+		session().data().applyUpdate(update.c_updateChatDefaultBannedRights());
+	} break;
+
+	case mtpc_updateUserStatus: {
+		auto &d = update.c_updateUserStatus();
+		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
+			switch (d.vstatus().type()) {
+			case mtpc_userStatusEmpty: user->onlineTill = 0; break;
+			case mtpc_userStatusRecently:
+				if (user->onlineTill > -10) { // don't modify pseudo-online
+					user->onlineTill = -2;
+				}
+			break;
+			case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
+			case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
+			case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break;
+			case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break;
+			}
+			Notify::peerUpdatedDelayed(
+				user,
+				Notify::PeerUpdate::Flag::UserOnlineChanged);
+		}
+		if (d.vuser_id().v == session().userId()) {
+			if (d.vstatus().type() == mtpc_userStatusOffline
+				|| d.vstatus().type() == mtpc_userStatusEmpty) {
+				updateOnline(true);
+				if (d.vstatus().type() == mtpc_userStatusOffline) {
+					cSetOtherOnline(
+						d.vstatus().c_userStatusOffline().vwas_online().v);
+				}
+			} else if (d.vstatus().type() == mtpc_userStatusOnline) {
+				cSetOtherOnline(
+					d.vstatus().c_userStatusOnline().vexpires().v);
+			}
+		}
+	} break;
+
+	case mtpc_updateUserName: {
+		auto &d = update.c_updateUserName();
+		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
+			if (!user->isContact()) {
+				user->setName(
+					TextUtilities::SingleLine(qs(d.vfirst_name())),
+					TextUtilities::SingleLine(qs(d.vlast_name())),
+					user->nameOrPhone,
+					TextUtilities::SingleLine(qs(d.vusername())));
+			} else {
+				user->setName(
+					TextUtilities::SingleLine(user->firstName),
+					TextUtilities::SingleLine(user->lastName),
+					user->nameOrPhone,
+					TextUtilities::SingleLine(qs(d.vusername())));
+			}
+		}
+	} break;
+
+	case mtpc_updateUserPhoto: {
+		auto &d = update.c_updateUserPhoto();
+		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
+			user->setPhoto(d.vphoto());
+			user->loadUserpic();
+			if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) {
+				session().storage().remove(Storage::UserPhotosRemoveAfter(
+					user->bareId(),
+					user->userpicPhotoId()));
+			} else {
+				session().storage().add(Storage::UserPhotosAddNew(
+					user->bareId(),
+					user->userpicPhotoId()));
+			}
+		}
+	} break;
+
+	case mtpc_updatePeerSettings: {
+		const auto &d = update.c_updatePeerSettings();
+		const auto peerId = peerFromMTP(d.vpeer());
+		if (const auto peer = session().data().peerLoaded(peerId)) {
+			const auto settings = d.vsettings().match([](
+					const MTPDpeerSettings &data) {
+				return data.vflags().v;
+			});
+			peer->setSettings(settings);
+		}
+	} break;
+
+	case mtpc_updateNotifySettings: {
+		auto &d = update.c_updateNotifySettings();
+		session().data().applyNotifySetting(d.vpeer(), d.vnotify_settings());
+	} break;
+
+	case mtpc_updateDcOptions: {
+		auto &d = update.c_updateDcOptions();
+		Core::App().dcOptions()->addFromList(d.vdc_options());
+	} break;
+
+	case mtpc_updateConfig: {
+		session().mtp()->requestConfig();
+	} break;
+
+	case mtpc_updateUserPhone: {
+		const auto &d = update.c_updateUserPhone();
+		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
+			const auto newPhone = qs(d.vphone());
+			if (newPhone != user->phone()) {
+				user->setPhone(newPhone);
+				user->setName(
+					user->firstName,
+					user->lastName,
+					((user->isContact()
+						|| user->isServiceUser()
+						|| user->isSelf()
+						|| user->phone().isEmpty())
+						? QString()
+						: App::formatPhone(user->phone())),
+					user->username);
+
+				Notify::peerUpdatedDelayed(
+					user,
+					Notify::PeerUpdate::Flag::UserPhoneChanged);
+			}
+		}
+	} break;
+
+	case mtpc_updateNewEncryptedMessage: {
+		auto &d = update.c_updateNewEncryptedMessage();
+	} break;
+
+	case mtpc_updateEncryptedChatTyping: {
+		auto &d = update.c_updateEncryptedChatTyping();
+	} break;
+
+	case mtpc_updateEncryption: {
+		auto &d = update.c_updateEncryption();
+	} break;
+
+	case mtpc_updateEncryptedMessagesRead: {
+		auto &d = update.c_updateEncryptedMessagesRead();
+	} break;
+
+	case mtpc_updatePhoneCall: {
+		session().calls().handleUpdate(update.c_updatePhoneCall());
+	} break;
+
+	case mtpc_updateUserBlocked: {
+		const auto &d = update.c_updateUserBlocked();
+		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
+			user->setIsBlocked(mtpIsTrue(d.vblocked()));
+		}
+	} break;
+
+	case mtpc_updateServiceNotification: {
+		const auto &d = update.c_updateServiceNotification();
+		const auto text = TextWithEntities {
+			qs(d.vmessage()),
+			Api::EntitiesFromMTP(&session(), d.ventities().v)
+		};
+		if (IsForceLogoutNotification(d)) {
+			Core::App().forceLogOut(text);
+		} else if (d.is_popup()) {
+			const auto &windows = session().windows();
+			if (!windows.empty()) {
+				windows.front()->window().show(Box<InformBox>(text));
+			}
+		} else {
+			session().data().serviceNotification(text, d.vmedia());
+			session().data().checkNewAuthorization();
+		}
+	} break;
+
+	case mtpc_updatePrivacy: {
+		auto &d = update.c_updatePrivacy();
+		const auto allChatsLoaded = [&](const MTPVector<MTPint> &ids) {
+			for (const auto &chatId : ids.v) {
+				if (!session().data().chatLoaded(chatId.v)
+					&& !session().data().channelLoaded(chatId.v)) {
+					return false;
+				}
+			}
+			return true;
+		};
+		const auto allLoaded = [&] {
+			for (const auto &rule : d.vrules().v) {
+				const auto loaded = rule.match([&](
+					const MTPDprivacyValueAllowChatParticipants & data) {
+					return allChatsLoaded(data.vchats());
+				}, [&](const MTPDprivacyValueDisallowChatParticipants & data) {
+					return allChatsLoaded(data.vchats());
+				}, [](auto &&) { return true; });
+				if (!loaded) {
+					return false;
+				}
+			}
+			return true;
+		};
+		if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) {
+			if (allLoaded()) {
+				session().api().handlePrivacyChange(*key, d.vrules());
+			} else {
+				session().api().reloadPrivacy(*key);
+			}
+		}
+	} break;
+
+	case mtpc_updatePinnedDialogs: {
+		const auto &d = update.c_updatePinnedDialogs();
+		const auto folderId = d.vfolder_id().value_or_empty();
+		const auto loaded = !folderId
+			|| (session().data().folderLoaded(folderId) != nullptr);
+		const auto folder = folderId
+			? session().data().folder(folderId).get()
+			: nullptr;
+		const auto done = [&] {
+			const auto list = d.vorder();
+			if (!list) {
+				return false;
+			}
+			const auto &order = list->v;
+			const auto notLoaded = [&](const MTPDialogPeer &peer) {
+				return peer.match([&](const MTPDdialogPeer &data) {
+					return !session().data().historyLoaded(
+						peerFromMTP(data.vpeer()));
+				}, [&](const MTPDdialogPeerFolder &data) {
+					if (folderId) {
+						LOG(("API Error: "
+							"updatePinnedDialogs has nested folders."));
+						return true;
+					}
+					return !session().data().folderLoaded(data.vfolder_id().v);
+				});
+			};
+			const auto allLoaded = ranges::find_if(order, notLoaded)
+				== order.end();
+			if (!allLoaded) {
+				return false;
+			}
+			session().data().applyPinnedChats(folder, order);
+			return true;
+		}();
+		if (!done) {
+			session().api().requestPinnedDialogs(folder);
+		}
+		if (!loaded) {
+			session().data().histories().requestDialogEntry(folder);
+		}
+	} break;
+
+	case mtpc_updateDialogPinned: {
+		const auto &d = update.c_updateDialogPinned();
+		const auto folderId = d.vfolder_id().value_or_empty();
+		const auto folder = folderId
+			? session().data().folder(folderId).get()
+			: nullptr;
+		const auto done = d.vpeer().match([&](const MTPDdialogPeer &data) {
+			const auto id = peerFromMTP(data.vpeer());
+			if (const auto history = session().data().historyLoaded(id)) {
+				history->applyPinnedUpdate(d);
+				return true;
+			}
+			DEBUG_LOG(("API Error: "
+				"pinned chat not loaded for peer %1, folder: %2"
+				).arg(id
+				).arg(folderId
+				));
+			return false;
+		}, [&](const MTPDdialogPeerFolder &data) {
+			if (folderId != 0) {
+				DEBUG_LOG(("API Error: Nested folders updateDialogPinned."));
+				return false;
+			}
+			const auto id = data.vfolder_id().v;
+			if (const auto folder = session().data().folderLoaded(id)) {
+				folder->applyPinnedUpdate(d);
+				return true;
+			}
+			DEBUG_LOG(("API Error: "
+				"pinned folder not loaded for folderId %1, folder: %2"
+				).arg(id
+				).arg(folderId
+				));
+			return false;
+		});
+		if (!done) {
+			session().api().requestPinnedDialogs(folder);
+		}
+	} break;
+
+	case mtpc_updateChannel: {
+		auto &d = update.c_updateChannel();
+		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
+			channel->inviter = UserId(0);
+			if (channel->amIn()) {
+				if (channel->isMegagroup()
+					&& !channel->amCreator()
+					&& !channel->hasAdminRights()) {
+					channel->updateFullForced();
+				}
+				const auto history = channel->owner().history(channel);
+				//if (const auto feed = channel->feed()) { // #feed
+				//	feed->requestChatListMessage();
+				//	if (!feed->unreadCountKnown()) {
+				//		feed->owner().histories().requestDialogEntry(feed);
+				//	}
+				//} else {
+					history->requestChatListMessage();
+					if (!history->unreadCountKnown()) {
+						history->owner().histories().requestDialogEntry(history);
+					}
+				//}
+				if (!channel->amCreator()) {
+					session().api().requestSelfParticipant(channel);
+				}
+			}
+		}
+	} break;
+
+	case mtpc_updateChannelTooLong: {
+		const auto &d = update.c_updateChannelTooLong();
+		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
+			const auto pts = d.vpts();
+			if (!pts || channel->pts() < pts->v) {
+				getChannelDifference(channel);
+			}
+		}
+	} break;
+
+	case mtpc_updateChannelMessageViews: {
+		auto &d = update.c_updateChannelMessageViews();
+		if (auto item = session().data().message(d.vchannel_id().v, d.vid().v)) {
+			item->setViewsCount(d.vviews().v);
+		}
+	} break;
+
+	case mtpc_updateChannelAvailableMessages: {
+		auto &d = update.c_updateChannelAvailableMessages();
+		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
+			channel->setAvailableMinId(d.vavailable_min_id().v);
+			if (const auto history = session().data().historyLoaded(channel)) {
+				history->clearUpTill(d.vavailable_min_id().v);
+			}
+		}
+	} break;
+
+	// Pinned message.
+	case mtpc_updateUserPinnedMessage: {
+		const auto &d = update.c_updateUserPinnedMessage();
+		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
+			user->setPinnedMessageId(d.vid().v);
+		}
+	} break;
+
+	case mtpc_updateChatPinnedMessage: {
+		const auto &d = update.c_updateChatPinnedMessage();
+		if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) {
+			const auto status = chat->applyUpdateVersion(d.vversion().v);
+			if (status == ChatData::UpdateStatus::Good) {
+				chat->setPinnedMessageId(d.vid().v);
+			}
+		}
+	} break;
+
+	case mtpc_updateChannelPinnedMessage: {
+		const auto &d = update.c_updateChannelPinnedMessage();
+		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
+			channel->setPinnedMessageId(d.vid().v);
+		}
+	} break;
+
+	////// Cloud sticker sets
+	case mtpc_updateNewStickerSet: {
+		const auto &d = update.c_updateNewStickerSet();
+		session().data().stickers().newSetReceived(d.vstickerset());
+	} break;
+
+	case mtpc_updateStickerSetsOrder: {
+		auto &d = update.c_updateStickerSetsOrder();
+		if (!d.is_masks()) {
+			const auto &order = d.vorder().v;
+			const auto &sets = session().data().stickers().sets();
+			Data::StickersSetsOrder result;
+			for (const auto &item : order) {
+				if (sets.find(item.v) == sets.cend()) {
+					break;
+				}
+				result.push_back(item.v);
+			}
+			if (result.size() != session().data().stickers().setsOrder().size()
+				|| result.size() != order.size()) {
+				session().data().stickers().setLastUpdate(0);
+				session().api().updateStickers();
+			} else {
+				session().data().stickers().setsOrderRef() = std::move(result);
+				session().local().writeInstalledStickers();
+				session().data().stickers().notifyUpdated();
+			}
+		}
+	} break;
+
+	case mtpc_updateStickerSets: {
+		session().data().stickers().setLastUpdate(0);
+		session().api().updateStickers();
+	} break;
+
+	case mtpc_updateRecentStickers: {
+		session().data().stickers().setLastRecentUpdate(0);
+		session().api().updateStickers();
+	} break;
+
+	case mtpc_updateFavedStickers: {
+		session().data().stickers().setLastFavedUpdate(0);
+		session().api().updateStickers();
+	} break;
+
+	case mtpc_updateReadFeaturedStickers: {
+		// We read some of the featured stickers, perhaps not all of them.
+		// Here we don't know what featured sticker sets were read, so we
+		// request all of them once again.
+		session().data().stickers().setLastFeaturedUpdate(0);
+		session().api().updateStickers();
+	} break;
+
+	////// Cloud saved GIFs
+	case mtpc_updateSavedGifs: {
+		session().data().stickers().setLastSavedGifsUpdate(0);
+		session().api().updateStickers();
+	} break;
+
+	////// Cloud drafts
+	case mtpc_updateDraftMessage: {
+		const auto &data = update.c_updateDraftMessage();
+		const auto peerId = peerFromMTP(data.vpeer());
+		data.vdraft().match([&](const MTPDdraftMessage &data) {
+			Data::ApplyPeerCloudDraft(&session(), peerId, data);
+		}, [&](const MTPDdraftMessageEmpty &data) {
+			Data::ClearPeerCloudDraft(
+				&session(),
+				peerId,
+				data.vdate().value_or_empty());
+		});
+	} break;
+
+	////// Cloud langpacks
+	case mtpc_updateLangPack: {
+		const auto &data = update.c_updateLangPack();
+		Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference());
+	} break;
+
+	case mtpc_updateLangPackTooLong: {
+		const auto &data = update.c_updateLangPackTooLong();
+		const auto code = qs(data.vlang_code());
+		if (!code.isEmpty()) {
+			Lang::CurrentCloudManager().requestLangPackDifference(code);
+		}
+	} break;
+
+	////// Cloud themes
+	case mtpc_updateTheme: {
+		const auto &data = update.c_updateTheme();
+		session().data().cloudThemes().applyUpdate(data.vtheme());
+	} break;
+
+	}
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h
new file mode 100644
index 000000000..c85b94494
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_updates.h
@@ -0,0 +1,172 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "mtproto/mtproto_rpc_sender.h"
+#include "data/data_pts_waiter.h"
+#include "base/timer.h"
+
+class ApiWrap;
+class History;
+
+namespace Main {
+class Session;
+} // namespace Main
+
+namespace Api {
+
+class Updates final {
+public:
+	explicit Updates(not_null<Main::Session*> session);
+
+	[[nodiscard]] Main::Session &session() const;
+	[[nodiscard]] ApiWrap &api() const;
+
+	void applyUpdates(
+		const MTPUpdates &updates,
+		uint64 sentMessageRandomId = 0);
+	void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
+	void applyUpdateNoPtsCheck(const MTPUpdate &update);
+
+	void updateOnline();
+	[[nodiscard]] bool isIdle() const;
+	void checkIdleFinish();
+	bool lastWasOnline() const;
+	crl::time lastSetOnline() const;
+	bool isQuitPrevent();
+
+	bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates);
+	bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update);
+	bool updateAndApply(int32 pts, int32 ptsCount);
+
+	void checkLastUpdate(bool afterSleep);
+
+	// ms <= 0 - stop timer
+	void ptsWaiterStartTimerFor(ChannelData *channel, crl::time ms);
+
+	void getDifference();
+	void requestChannelRangeDifference(not_null<History*> history);
+
+	void addActiveChat(rpl::producer<PeerData*> chat);
+
+private:
+	enum class ChannelDifferenceRequest {
+		Unknown,
+		PtsGapOrShortPoll,
+		AfterFail,
+	};
+
+	struct ActiveChatTracker {
+		PeerData *peer = nullptr;
+		rpl::lifetime lifetime;
+	};
+
+	void channelRangeDifferenceSend(
+		not_null<ChannelData*> channel,
+		MsgRange range,
+		int32 pts);
+	void channelRangeDifferenceDone(
+		not_null<ChannelData*> channel,
+		MsgRange range,
+		const MTPupdates_ChannelDifference &result);
+
+	void updateOnline(bool gotOtherOffline);
+	void sendPing();
+	void getDifferenceByPts();
+	void getDifferenceAfterFail();
+
+	[[nodiscard]] bool requestingDifference() const {
+		return _ptsWaiter.requesting();
+	}
+	void getChannelDifference(
+		not_null<ChannelData*> channel,
+		ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown);
+	void differenceDone(const MTPupdates_Difference &result);
+	void differenceFail(const RPCError &error);
+	void feedDifference(
+		const MTPVector<MTPUser> &users,
+		const MTPVector<MTPChat> &chats,
+		const MTPVector<MTPMessage> &msgs,
+		const MTPVector<MTPUpdate> &other);
+	void stateDone(const MTPupdates_State &state);
+	void setState(int32 pts, int32 date, int32 qts, int32 seq);
+	void channelDifferenceDone(
+		not_null<ChannelData*> channel,
+		const MTPupdates_ChannelDifference &diff);
+	void channelDifferenceFail(
+		not_null<ChannelData*> channel,
+		const RPCError &error);
+	void failDifferenceStartTimerFor(ChannelData *channel);
+	void feedChannelDifference(const MTPDupdates_channelDifference &data);
+
+	void mtpUpdateReceived(const MTPUpdates &updates);
+	void mtpNewSessionCreated();
+	void feedUpdateVector(
+		const MTPVector<MTPUpdate> &updates,
+		bool skipMessageIds = false);
+	// Doesn't call sendHistoryChangeNotifications itself.
+	void feedMessageIds(const MTPVector<MTPUpdate> &updates);
+	// Doesn't call sendHistoryChangeNotifications itself.
+	void feedUpdate(const MTPUpdate &update);
+
+	bool whenGetDiffChanged(
+		ChannelData *channel,
+		int32 ms,
+		base::flat_map<not_null<ChannelData*>, crl::time> &whenMap,
+		crl::time &curTime);
+
+	const not_null<Main::Session*> _session;
+
+	int32 _updatesDate = 0;
+	int32 _updatesQts = -1;
+	int32 _updatesSeq = 0;
+	base::Timer _noUpdatesTimer;
+	base::Timer _onlineTimer;
+
+	PtsWaiter _ptsWaiter;
+
+	base::flat_map<not_null<ChannelData*>, crl::time> _whenGetDiffByPts;
+	base::flat_map<not_null<ChannelData*>, crl::time> _whenGetDiffAfterFail;
+	crl::time _getDifferenceTimeByPts = 0;
+	crl::time _getDifferenceTimeAfterFail = 0;
+
+	base::Timer _byPtsTimer;
+
+	base::flat_map<int32, MTPUpdates> _bySeqUpdates;
+	base::Timer _bySeqTimer;
+
+	base::Timer _byMinChannelTimer;
+
+	// growing timeout for getDifference calls, if it fails
+	crl::time _failDifferenceTimeout = 1;
+	// growing timeout for getChannelDifference calls, if it fails
+	base::flat_map<
+		not_null<ChannelData*>,
+		crl::time> _channelFailDifferenceTimeout;
+	base::Timer _failDifferenceTimer;
+
+	base::flat_map<
+		not_null<ChannelData*>,
+		mtpRequestId> _rangeDifferenceRequests;
+
+	crl::time _lastUpdateTime = 0;
+	bool _handlingChannelDifference = false;
+
+	base::flat_map<int, ActiveChatTracker> _activeChats;
+
+	mtpRequestId _onlineRequest = 0;
+	base::Timer _idleFinishTimer;
+	crl::time _lastSetOnline = 0;
+	bool _lastWasOnline = false;
+	bool _isIdle = false;
+
+	rpl::lifetime _lifetime;
+
+};
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 19f33dfa5..e98047ca5 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "api/api_text_entities.h"
 #include "api/api_self_destruct.h"
 #include "api/api_sensitive_content.h"
+#include "api/api_updates.h"
 #include "data/stickers/data_stickers.h"
 #include "data/data_drafts.h"
 #include "data/data_photo.h"
@@ -272,6 +273,10 @@ Storage::Account &ApiWrap::local() const {
 	return _session->local();
 }
 
+Api::Updates &ApiWrap::updates() const {
+	return _session->updates();
+}
+
 void ApiWrap::setupSupportMode() {
 	if (!_session->supportMode()) {
 		return;
@@ -491,12 +496,6 @@ void ApiWrap::importChatInvite(const QString &hash) {
 	}).send();
 }
 
-void ApiWrap::applyUpdates(
-		const MTPUpdates &updates,
-		uint64 sentMessageRandomId) {
-	App::main()->feedUpdates(updates, sentMessageRandomId);
-}
-
 void ApiWrap::savePinnedOrder(Data::Folder *folder) {
 	const auto &order = _session->data().pinnedChatsOrder(
 		folder,
@@ -2321,8 +2320,8 @@ void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
 		request(MTPmessages_DeleteChatUser(
 			chat->inputChat,
 			_session->user()->inputUser
-		)).done([=](const MTPUpdates &updates) {
-			applyUpdates(updates);
+		)).done([=](const MTPUpdates &result) {
+			applyUpdates(result);
 			deleteHistory(peer, false, revoke);
 		}).fail([=](const RPCError &error) {
 			deleteHistory(peer, false, revoke);
@@ -2392,6 +2391,12 @@ void ApiWrap::deleteHistory(
 	}
 }
 
+void ApiWrap::applyUpdates(
+		const MTPUpdates &updates,
+		uint64 sentMessageRandomId) {
+	this->updates().applyUpdates(updates, sentMessageRandomId);
+}
+
 int ApiWrap::applyAffectedHistory(
 		not_null<PeerData*> peer,
 		const MTPmessages_AffectedHistory &result) {
@@ -2399,7 +2404,7 @@ int ApiWrap::applyAffectedHistory(
 	if (const auto channel = peer->asChannel()) {
 		channel->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
 	} else {
-		App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
+		updates().updateAndApply(data.vpts().v, data.vpts_count().v);
 	}
 	return data.voffset().v;
 }
@@ -2418,7 +2423,26 @@ void ApiWrap::applyAffectedMessages(
 void ApiWrap::applyAffectedMessages(
 		const MTPmessages_AffectedMessages &result) {
 	const auto &data = result.c_messages_affectedMessages();
-	App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
+	updates().updateAndApply(data.vpts().v, data.vpts_count().v);
+}
+
+void ApiWrap::saveCurrentDraftToCloud() {
+	Core::App().saveCurrentDraftsToHistories();
+
+	for (const auto controller : session().windows()) {
+		if (const auto peer = controller->activeChatCurrent().peer()) {
+			if (const auto history = session().data().historyLoaded(peer)) {
+				session().local().writeDrafts(history);
+
+				const auto localDraft = history->localDraft();
+				const auto cloudDraft = history->cloudDraft();
+				if (!Data::draftsAreEqual(localDraft, cloudDraft)
+					&& !session().supportMode()) {
+					saveDraftToCloudDelayed(history);
+				}
+			}
+		}
+	}
 }
 
 void ApiWrap::saveDraftsToCloud() {
@@ -2713,98 +2737,6 @@ void ApiWrap::requestParticipantsCountDelayed(
 		[=] { channel->updateFullForced(); });
 }
 
-void ApiWrap::requestChannelRangeDifference(not_null<History*> history) {
-	Expects(history->isChannel());
-
-	const auto channel = history->peer->asChannel();
-	if (const auto requestId = _rangeDifferenceRequests.take(channel)) {
-		request(*requestId).cancel();
-	}
-	const auto range = history->rangeForDifferenceRequest();
-	if (!(range.from < range.till) || !channel->pts()) {
-		return;
-	}
-
-	MTP_LOG(0, ("getChannelDifference { good - "
-		"after channelDifferenceTooLong was received, "
-		"validating history part }%1").arg(cTestMode() ? " TESTMODE" : ""));
-	channelRangeDifferenceSend(channel, range, channel->pts());
-}
-
-void ApiWrap::channelRangeDifferenceSend(
-		not_null<ChannelData*> channel,
-		MsgRange range,
-		int32 pts) {
-	Expects(range.from < range.till);
-
-	const auto limit = range.till - range.from;
-	const auto filter = MTP_channelMessagesFilter(
-		MTP_flags(0),
-		MTP_vector<MTPMessageRange>(1, MTP_messageRange(
-			MTP_int(range.from),
-			MTP_int(range.till - 1))));
-	const auto requestId = request(MTPupdates_GetChannelDifference(
-		MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force),
-		channel->inputChannel,
-		filter,
-		MTP_int(pts),
-		MTP_int(limit)
-	)).done([=](const MTPupdates_ChannelDifference &result) {
-		_rangeDifferenceRequests.remove(channel);
-		channelRangeDifferenceDone(channel, range, result);
-	}).fail([=](const RPCError &error) {
-		_rangeDifferenceRequests.remove(channel);
-	}).send();
-	_rangeDifferenceRequests.emplace(channel, requestId);
-}
-
-void ApiWrap::channelRangeDifferenceDone(
-		not_null<ChannelData*> channel,
-		MsgRange range,
-		const MTPupdates_ChannelDifference &result) {
-	auto nextRequestPts = int32(0);
-	auto isFinal = true;
-
-	switch (result.type()) {
-	case mtpc_updates_channelDifferenceEmpty: {
-		const auto &d = result.c_updates_channelDifferenceEmpty();
-		nextRequestPts = d.vpts().v;
-		isFinal = d.is_final();
-	} break;
-
-	case mtpc_updates_channelDifferenceTooLong: {
-		const auto &d = result.c_updates_channelDifferenceTooLong();
-
-		_session->data().processUsers(d.vusers());
-		_session->data().processChats(d.vchats());
-
-		nextRequestPts = d.vdialog().match([&](const MTPDdialog &data) {
-			return data.vpts().value_or_empty();
-		}, [&](const MTPDdialogFolder &data) {
-			return 0;
-		});
-		isFinal = d.is_final();
-	} break;
-
-	case mtpc_updates_channelDifference: {
-		const auto &d = result.c_updates_channelDifference();
-
-		App::main()->feedChannelDifference(d);
-
-		nextRequestPts = d.vpts().v;
-		isFinal = d.is_final();
-	} break;
-	}
-
-	if (!isFinal && nextRequestPts) {
-		MTP_LOG(0, ("getChannelDifference { "
-			"good - after not final channelDifference was received, "
-			"validating history part }%1"
-			).arg(cTestMode() ? " TESTMODE" : ""));
-		channelRangeDifferenceSend(channel, range, nextRequestPts);
-	}
-}
-
 template <typename Request>
 void ApiWrap::requestFileReference(
 		Data::FileOrigin origin,
@@ -3418,224 +3350,6 @@ void ApiWrap::parseRecentChannelParticipants(
 	}, std::move(callbackNotModified));
 }
 
-void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
-	switch (updates.type()) {
-	case mtpc_updateShortMessage: {
-		const auto &d = updates.c_updateShortMessage();
-		const auto flags = mtpCastFlags(d.vflags().v)
-			| MTPDmessage::Flag::f_from_id;
-		const auto peerUserId = d.is_out()
-			? d.vuser_id()
-			: MTP_int(_session->userId());
-		const auto fwd = d.vfwd_from();
-		_session->data().addNewMessage(
-			MTP_message(
-				MTP_flags(flags),
-				d.vid(),
-				d.is_out() ? MTP_int(_session->userId()) : d.vuser_id(),
-				MTP_peerUser(peerUserId),
-				fwd ? (*fwd) : MTPMessageFwdHeader(),
-				MTP_int(d.vvia_bot_id().value_or_empty()),
-				MTP_int(d.vreply_to_msg_id().value_or_empty()),
-				d.vdate(),
-				d.vmessage(),
-				MTP_messageMediaEmpty(),
-				MTPReplyMarkup(),
-				MTP_vector<MTPMessageEntity>(d.ventities().value_or_empty()),
-				MTPint(),
-				MTPint(),
-				MTPstring(),
-				MTPlong(),
-				//MTPMessageReactions(),
-				MTPVector<MTPRestrictionReason>()),
-			MTPDmessage_ClientFlags(),
-			NewMessageType::Unread);
-	} break;
-
-	case mtpc_updateShortChatMessage: {
-		const auto &d = updates.c_updateShortChatMessage();
-		const auto flags = mtpCastFlags(d.vflags().v) | MTPDmessage::Flag::f_from_id;
-		const auto fwd = d.vfwd_from();
-		_session->data().addNewMessage(
-			MTP_message(
-				MTP_flags(flags),
-				d.vid(),
-				d.vfrom_id(),
-				MTP_peerChat(d.vchat_id()),
-				fwd ? (*fwd) : MTPMessageFwdHeader(),
-				MTP_int(d.vvia_bot_id().value_or_empty()),
-				MTP_int(d.vreply_to_msg_id().value_or_empty()),
-				d.vdate(),
-				d.vmessage(),
-				MTP_messageMediaEmpty(),
-				MTPReplyMarkup(),
-				MTP_vector<MTPMessageEntity>(d.ventities().value_or_empty()),
-				MTPint(),
-				MTPint(),
-				MTPstring(),
-				MTPlong(),
-				//MTPMessageReactions(),
-				MTPVector<MTPRestrictionReason>()),
-			MTPDmessage_ClientFlags(),
-			NewMessageType::Unread);
-	} break;
-
-	case mtpc_updateShortSentMessage: {
-		auto &d = updates.c_updateShortSentMessage();
-		Q_UNUSED(d); // Sent message data was applied anyway.
-	} break;
-
-	default: Unexpected("Type in applyUpdatesNoPtsCheck()");
-	}
-}
-
-void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) {
-	switch (update.type()) {
-	case mtpc_updateNewMessage: {
-		auto &d = update.c_updateNewMessage();
-		auto needToAdd = true;
-		if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
-			const auto &data = d.vmessage().c_message();
-			if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks
-				LOG(("Skipping message, because it is already in blocks!"));
-				needToAdd = false;
-			}
-			if (needToAdd && !data.is_from_scheduled()) {
-				// If we still need to add a new message,
-				// we should first check if this message is in
-				// the list of scheduled messages.
-				// This is necessary to correctly update the file reference.
-				// Note that when a message is scheduled until online
-				// while the recipient is already online, the server sends
-				// an ordinary new message with skipped "from_scheduled" flag.
-				_session->data().scheduledMessages().checkEntitiesAndUpdate(
-					data);
-			}
-		}
-		if (needToAdd) {
-			_session->data().addNewMessage(
-				d.vmessage(),
-				MTPDmessage_ClientFlags(),
-				NewMessageType::Unread);
-		}
-	} break;
-
-	case mtpc_updateReadMessagesContents: {
-		const auto &d = update.c_updateReadMessagesContents();
-		auto possiblyReadMentions = base::flat_set<MsgId>();
-		for (const auto &msgId : d.vmessages().v) {
-			if (const auto item = _session->data().message(NoChannel, msgId.v)) {
-				if (item->isUnreadMedia() || item->isUnreadMention()) {
-					item->markMediaRead();
-					_session->data().requestItemRepaint(item);
-
-					if (item->out()
-						&& item->history()->peer->isUser()
-						&& !App::main()->requestingDifference()) {
-						item->history()->peer->asUser()->madeAction(base::unixtime::now());
-					}
-				}
-			} else {
-				// Perhaps it was an unread mention!
-				possiblyReadMentions.insert(msgId.v);
-			}
-		}
-		checkForUnreadMentions(possiblyReadMentions);
-	} break;
-
-	case mtpc_updateReadHistoryInbox: {
-		const auto &d = update.c_updateReadHistoryInbox();
-		const auto peer = peerFromMTP(d.vpeer());
-		if (const auto history = _session->data().historyLoaded(peer)) {
-			const auto folderId = d.vfolder_id().value_or_empty();
-			history->applyInboxReadUpdate(
-				folderId,
-				d.vmax_id().v,
-				d.vstill_unread_count().v);
-		}
-	} break;
-
-	case mtpc_updateReadHistoryOutbox: {
-		const auto &d = update.c_updateReadHistoryOutbox();
-		const auto peer = peerFromMTP(d.vpeer());
-		if (const auto history = _session->data().historyLoaded(peer)) {
-			history->outboxRead(d.vmax_id().v);
-			if (!App::main()->requestingDifference()) {
-				if (const auto user = history->peer->asUser()) {
-					user->madeAction(base::unixtime::now());
-				}
-			}
-		}
-	} break;
-
-	case mtpc_updateWebPage: {
-		auto &d = update.c_updateWebPage();
-		Q_UNUSED(d); // Web page was updated anyway.
-	} break;
-
-	case mtpc_updateFolderPeers: {
-		const auto &data = update.c_updateFolderPeers();
-		auto &owner = _session->data();
-		for (const auto &peer : data.vfolder_peers().v) {
-			peer.match([&](const MTPDfolderPeer &data) {
-				const auto peerId = peerFromMTP(data.vpeer());
-				if (const auto history = owner.historyLoaded(peerId)) {
-					if (const auto folderId = data.vfolder_id().v) {
-						history->setFolder(owner.folder(folderId));
-					} else {
-						history->clearFolder();
-					}
-				}
-			});
-		}
-	} break;
-
-	case mtpc_updateDeleteMessages: {
-		auto &d = update.c_updateDeleteMessages();
-		_session->data().processMessagesDeleted(NoChannel, d.vmessages().v);
-	} break;
-
-	case mtpc_updateNewChannelMessage: {
-		auto &d = update.c_updateNewChannelMessage();
-		auto needToAdd = true;
-		if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
-			if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks
-				LOG(("Skipping message, because it is already in blocks!"));
-				needToAdd = false;
-			}
-		}
-		if (needToAdd) {
-			_session->data().addNewMessage(
-				d.vmessage(),
-				MTPDmessage_ClientFlags(),
-				NewMessageType::Unread);
-		}
-	} break;
-
-	case mtpc_updateEditChannelMessage: {
-		auto &d = update.c_updateEditChannelMessage();
-		_session->data().updateEditedMessage(d.vmessage());
-	} break;
-
-	case mtpc_updateEditMessage: {
-		auto &d = update.c_updateEditMessage();
-		_session->data().updateEditedMessage(d.vmessage());
-	} break;
-
-	case mtpc_updateChannelWebPage: {
-		auto &d = update.c_updateChannelWebPage();
-		Q_UNUSED(d); // Web page was updated anyway.
-	} break;
-
-	case mtpc_updateDeleteChannelMessages: {
-		auto &d = update.c_updateDeleteChannelMessages();
-		_session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v);
-	} break;
-
-	default: Unexpected("Type in applyUpdateNoPtsCheck()");
-	}
-}
-
 void ApiWrap::jumpToDate(Dialogs::Key chat, const QDate &date) {
 	if (const auto peer = chat.peer()) {
 		jumpToHistoryDate(peer, date);
@@ -4346,9 +4060,8 @@ void ApiWrap::forwardMessages(
 				MTP_vector<MTPlong>(randomIds),
 				peer->input,
 				MTP_int(action.options.scheduled)
-			)).done([=](
-					const MTPUpdates &updates) {
-				applyUpdates(updates);
+			)).done([=](const MTPUpdates &result) {
+				applyUpdates(result);
 				if (shared && !--shared->requestsLeft) {
 					shared->callback();
 				}
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 3d2a2467c..1169fc198 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -66,6 +66,8 @@ inline QString ToString(uint64 value) {
 
 } // namespace details
 
+class Updates;
+
 template <
 	typename ...Types,
 	typename = std::enable_if_t<(sizeof...(Types) > 0)>>
@@ -139,6 +141,7 @@ public:
 
 	[[nodiscard]] Main::Session &session() const;
 	[[nodiscard]] Storage::Account &local() const;
+	[[nodiscard]] Api::Updates &updates() const;
 
 	void applyUpdates(
 		const MTPUpdates &updates,
@@ -154,6 +157,8 @@ public:
 		MTPInputNotifyPeer peer,
 		const MTPPeerNotifySettings &settings);
 
+	void saveCurrentDraftToCloud();
+
 	void savePinnedOrder(Data::Folder *folder);
 	void toggleHistoryArchived(
 		not_null<History*> history,
@@ -194,7 +199,6 @@ public:
 	void requestBots(not_null<ChannelData*> channel);
 	void requestAdmins(not_null<ChannelData*> channel);
 	void requestParticipantsCountDelayed(not_null<ChannelData*> channel);
-	void requestChannelRangeDifference(not_null<History*> history);
 
 	using UpdatedFileReferences = Data::UpdatedFileReferences;
 	using FileReferencesHandler = FnMut<void(const UpdatedFileReferences&)>;
@@ -310,9 +314,6 @@ public:
 
 	bool isQuitPrevent();
 
-	void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
-	void applyUpdateNoPtsCheck(const MTPUpdate &update);
-
 	void jumpToDate(Dialogs::Key chat, const QDate &date);
 
 	void preloadEnoughUnreadMentions(not_null<History*> history);
@@ -554,15 +555,6 @@ private:
 		mtpRequestId req);
 	void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
 
-	void channelRangeDifferenceSend(
-		not_null<ChannelData*> channel,
-		MsgRange range,
-		int32 pts);
-	void channelRangeDifferenceDone(
-		not_null<ChannelData*> channel,
-		MsgRange range,
-		const MTPupdates_ChannelDifference &result);
-
 	void stickerSetDisenabled(mtpRequestId requestId);
 	void stickersSaveOrder();
 
@@ -709,10 +701,6 @@ private:
 
 	base::flat_set<not_null<ChannelData*>> _selfParticipantRequests;
 
-	base::flat_map<
-		not_null<ChannelData*>,
-		mtpRequestId> _rangeDifferenceRequests;
-
 	QMap<WebPageData*, mtpRequestId> _webPagesPending;
 	base::Timer _webPagesTimer;
 
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 2174869ac..8d1864451 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "main/main_session.h"
 #include "apiwrap.h"
+#include "api/api_updates.h"
 #include "calls/calls_instance.h"
 #include "lang/lang_file_parser.h"
 #include "lang/lang_translator.h"
@@ -464,13 +465,14 @@ void Application::forceLogOut(const TextWithEntities &explanation) {
 }
 
 void Application::checkLocalTime() {
-	if (crl::adjust_time()) {
+	const auto adjusted = crl::adjust_time();
+	if (adjusted) {
 		base::Timer::Adjust();
 		base::ConcurrentTimerEnvironment::Adjust();
 		base::unixtime::http_invalidate();
-		if (App::main()) App::main()->checkLastUpdate(true);
-	} else {
-		if (App::main()) App::main()->checkLastUpdate(false);
+	}
+	if (activeAccount().sessionExists()) {
+		activeAccount().session().updates().checkLastUpdate(adjusted);
 	}
 }
 
@@ -697,6 +699,9 @@ bool Application::passcodeLocked() const {
 
 void Application::updateNonIdle() {
 	_lastNonIdleTime = crl::now();
+	if (activeAccount().sessionExists()) {
+		activeAccount().session().updates().checkIdleFinish();
+	}
 }
 
 crl::time Application::lastNonIdleTime() const {
@@ -794,6 +799,25 @@ rpl::producer<bool> Application::lockValue() const {
 		_1 || _2);
 }
 
+bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
+	if (App::quitting() || !_window) {
+		return false;
+	} else if (const auto controller = _window->sessionController()) {
+		if (&controller->session() == session) {
+			return _window->widget()->isActive();
+		}
+	}
+	return false;
+}
+
+void Application::saveCurrentDraftsToHistories() {
+	if (!_window) {
+		return;
+	} else if (const auto controller = _window->sessionController()) {
+		controller->content()->saveFieldToHistoryLocalDraft();
+	}
+}
+
 Window::Controller *Application::activeWindow() const {
 	return _window.get();
 }
@@ -897,10 +921,8 @@ void Application::QuitAttempt() {
 	if (IsAppLaunched()
 		&& App().activeAccount().sessionExists()
 		&& !Sandbox::Instance().isSavingSession()) {
-		if (const auto mainwidget = App::main()) {
-			if (mainwidget->isQuitPrevent()) {
-				prevents = true;
-			}
+		if (App().activeAccount().session().updates().isQuitPrevent()) {
+			prevents = true;
 		}
 		if (App().activeAccount().session().api().isQuitPrevent()) {
 			prevents = true;
diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h
index 705e6c4f8..edf6d8739 100644
--- a/Telegram/SourceFiles/core/application.h
+++ b/Telegram/SourceFiles/core/application.h
@@ -37,6 +37,7 @@ void quit();
 
 namespace Main {
 class Account;
+class Session;
 } // namespace Main
 
 namespace Ui {
@@ -96,6 +97,8 @@ public:
 	}
 
 	// Windows interface.
+	bool hasActiveWindow(not_null<Main::Session*> session) const;
+	void saveCurrentDraftsToHistories();
 	[[nodiscard]] Window::Controller *activeWindow() const;
 	bool closeActiveWindow();
 	bool minimizeActiveWindow();
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 9a0fedc4b..1d3746f7a 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -46,7 +46,7 @@ void MegagroupInfo::setLocation(const ChannelLocation &location) {
 ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
 : PeerData(owner, id)
 , inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0)))
-, _ptsWaiter(&owner->session()) {
+, _ptsWaiter(&owner->session().updates()) {
 	Data::PeerFlagValue(
 		this,
 		MTPDchannel::Flag::f_megagroup
diff --git a/Telegram/SourceFiles/data/data_pts_waiter.cpp b/Telegram/SourceFiles/data/data_pts_waiter.cpp
index f385146fc..316ef0a41 100644
--- a/Telegram/SourceFiles/data/data_pts_waiter.cpp
+++ b/Telegram/SourceFiles/data/data_pts_waiter.cpp
@@ -7,12 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "data/data_pts_waiter.h"
 
-#include "mainwidget.h"
-#include "main/main_session.h"
-#include "apiwrap.h"
-#include "app.h"
+#include "api/api_updates.h"
 
-PtsWaiter::PtsWaiter(not_null<Main::Session*> session) : _session(session) {
+PtsWaiter::PtsWaiter(not_null<Api::Updates*> owner) : _owner(owner) {
 }
 
 uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) {
@@ -22,11 +19,9 @@ uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) {
 	).first->first;
 }
 
-void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) {
+void PtsWaiter::setWaitingForSkipped(ChannelData *channel, crl::time ms) {
 	if (ms >= 0) {
-		if (App::main()) {
-			App::main()->ptsWaiterStartTimerFor(channel, ms);
-		}
+		_owner->ptsWaiterStartTimerFor(channel, ms);
 		_waitingForSkipped = true;
 	} else {
 		_waitingForSkipped = false;
@@ -34,11 +29,9 @@ void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) {
 	}
 }
 
-void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) {
+void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, crl::time ms) {
 	if (ms >= 0) {
-		if (App::main()) {
-			App::main()->ptsWaiterStartTimerFor(channel, ms);
-		}
+		_owner->ptsWaiterStartTimerFor(channel, ms);
 		_waitingForShortPoll = true;
 	} else {
 		_waitingForShortPoll = false;
@@ -47,8 +40,8 @@ void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) {
 }
 
 void PtsWaiter::checkForWaiting(ChannelData *channel) {
-	if (!_waitingForSkipped && !_waitingForShortPoll && App::main()) {
-		App::main()->ptsWaiterStartTimerFor(channel, -1);
+	if (!_waitingForSkipped && !_waitingForShortPoll) {
+		_owner->ptsWaiterStartTimerFor(channel, -1);
 	}
 }
 
@@ -67,10 +60,10 @@ void PtsWaiter::applySkippedUpdates(ChannelData *channel) {
 	for (auto i = _queue.cbegin(), e = _queue.cend(); i != e; ++i) {
 		switch (i->second) {
 		case SkippedUpdate: {
-			_session->api().applyUpdateNoPtsCheck(_updateQueue[i->first]);
+			_owner->applyUpdateNoPtsCheck(_updateQueue[i->first]);
 		} break;
 		case SkippedUpdates: {
-			_session->api().applyUpdatesNoPtsCheck(_updatesQueue[i->first]);
+			_owner->applyUpdatesNoPtsCheck(_updatesQueue[i->first]);
 		} break;
 		}
 	}
@@ -136,7 +129,7 @@ bool PtsWaiter::updateAndApply(
 	}
 	if (!_waitingForSkipped || _queue.empty()) {
 		// Optimization - no need to put in queue and back.
-		_session->api().applyUpdatesNoPtsCheck(updates);
+		_owner->applyUpdatesNoPtsCheck(updates);
 	} else {
 		_updatesQueue.emplace(ptsKey(SkippedUpdates, pts), updates);
 		applySkippedUpdates(channel);
@@ -154,7 +147,7 @@ bool PtsWaiter::updateAndApply(
 	}
 	if (!_waitingForSkipped || _queue.empty()) {
 		// Optimization - no need to put in queue and back.
-		_session->api().applyUpdateNoPtsCheck(update);
+		_owner->applyUpdateNoPtsCheck(update);
 	} else {
 		_updateQueue.emplace(ptsKey(SkippedUpdate, pts), update);
 		applySkippedUpdates(channel);
diff --git a/Telegram/SourceFiles/data/data_pts_waiter.h b/Telegram/SourceFiles/data/data_pts_waiter.h
index 1d0568ec4..e35309aa7 100644
--- a/Telegram/SourceFiles/data/data_pts_waiter.h
+++ b/Telegram/SourceFiles/data/data_pts_waiter.h
@@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
-namespace Main {
-class Session;
-} // namespace Main
+namespace Api {
+class Updates;
+} // namespace Api
 
 enum PtsSkippedQueue {
 	SkippedUpdate,
@@ -18,7 +18,7 @@ enum PtsSkippedQueue {
 
 class PtsWaiter {
 public:
-	explicit PtsWaiter(not_null<Main::Session*> session);
+	explicit PtsWaiter(not_null<Api::Updates*> owner);
 
 	// 1s wait for skipped seq or pts in updates.
 	static constexpr auto kWaitForSkippedTimeout = 1000;
@@ -45,8 +45,8 @@ public:
 	bool waitingForShortPoll() const {
 		return _waitingForShortPoll;
 	}
-	void setWaitingForSkipped(ChannelData *channel, int32 ms); // < 0 - not waiting
-	void setWaitingForShortPoll(ChannelData *channel, int32 ms); // < 0 - not waiting
+	void setWaitingForSkipped(ChannelData *channel, crl::time ms); // < 0 - not waiting
+	void setWaitingForShortPoll(ChannelData *channel, crl::time ms); // < 0 - not waiting
 	int32 current() const{
 		return _good;
 	}
@@ -88,7 +88,7 @@ private:
 	uint64 ptsKey(PtsSkippedQueue queue, int32 pts);
 	void checkForWaiting(ChannelData *channel);
 
-	const not_null<Main::Session*> _session;
+	const not_null<Api::Updates*> _owner;
 	base::flat_map<uint64, PtsSkippedQueue> _queue;
 	base::flat_map<uint64, MTPUpdate> _updateQueue;
 	base::flat_map<uint64, MTPUpdates> _updatesQueue;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 2b016491e..b4aaaade7 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -3272,6 +3272,7 @@ void Session::notifyPollUpdateDelayed(not_null<PollData*> poll) {
 
 void Session::sendWebPageGamePollNotifications() {
 	for (const auto page : base::take(_webpagesUpdated)) {
+		_webpageUpdates.fire_copy(page);
 		const auto i = _webpageViews.find(page);
 		if (i != _webpageViews.end()) {
 			for (const auto view : i->second) {
@@ -3295,6 +3296,26 @@ void Session::sendWebPageGamePollNotifications() {
 	}
 }
 
+rpl::producer<not_null<WebPageData*>> Session::webPageUpdates() const {
+	return _webpageUpdates.events();
+}
+
+void Session::channelDifferenceTooLong(not_null<ChannelData*> channel) {
+	_channelDifferenceTooLong.fire_copy(channel);
+}
+
+rpl::producer<not_null<ChannelData*>> Session::channelDifferenceTooLong() const {
+	return _channelDifferenceTooLong.events();
+}
+
+void Session::historyOutboxRead(not_null<History*> history) {
+	_historyOutboxReads.fire_copy(history);
+}
+
+rpl::producer<not_null<History*>> Session::historyOutboxReads() const {
+	return _historyOutboxReads.events();
+}
+
 void Session::registerItemView(not_null<ViewElement*> view) {
 	_views[view->data()].push_back(view);
 }
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 508ffd1b8..16fa66e42 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -549,8 +549,15 @@ public:
 	void notifyWebPageUpdateDelayed(not_null<WebPageData*> page);
 	void notifyGameUpdateDelayed(not_null<GameData*> game);
 	void notifyPollUpdateDelayed(not_null<PollData*> poll);
-	bool hasPendingWebPageGamePollNotification() const;
+	[[nodiscard]] bool hasPendingWebPageGamePollNotification() const;
 	void sendWebPageGamePollNotifications();
+	[[nodiscard]] rpl::producer<not_null<WebPageData*>> webPageUpdates() const;
+
+	void channelDifferenceTooLong(not_null<ChannelData*> channel);
+	[[nodiscard]] rpl::producer<not_null<ChannelData*>> channelDifferenceTooLong() const;
+
+	void historyOutboxRead(not_null<History*> history);
+	[[nodiscard]] rpl::producer<not_null<History*>> historyOutboxReads() const;
 
 	void registerItemView(not_null<ViewElement*> view);
 	void unregisterItemView(not_null<ViewElement*> view);
@@ -868,6 +875,10 @@ private:
 	base::flat_set<not_null<GameData*>> _gamesUpdated;
 	base::flat_set<not_null<PollData*>> _pollsUpdated;
 
+	rpl::event_stream<not_null<WebPageData*>> _webpageUpdates;
+	rpl::event_stream<not_null<ChannelData*>> _channelDifferenceTooLong;
+	rpl::event_stream<not_null<History*>> _historyOutboxReads;
+
 	base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
 	base::Timer _pollsClosingTimer;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 7ef29a5de..6a3f387d0 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -320,6 +320,24 @@ Widget::Widget(
 	}, lifetime());
 }
 
+void Widget::setGeometryWithTopMoved(
+		const QRect &newGeometry,
+		int topDelta) {
+	_topDelta = topDelta;
+	bool willBeResized = (size() != newGeometry.size());
+	if (geometry() != newGeometry) {
+		auto weak = Ui::MakeWeak(this);
+		setGeometry(newGeometry);
+		if (!weak) {
+			return;
+		}
+	}
+	if (!willBeResized) {
+		resizeEvent(nullptr);
+	}
+	_topDelta = 0;
+}
+
 void Widget::setupScrollUpButton() {
 	_scrollToTop->setClickedCallback([=] {
 		if (_scrollToAnimation.animating()) {
@@ -707,8 +725,10 @@ void Widget::escape() {
 
 void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) {
 	if (!mayBlock) {
-		_loadMoreChats.destroy();
-		updateControlsGeometry();
+		if (_loadMoreChats) {
+			_loadMoreChats.destroy();
+			updateControlsGeometry();
+		}
 		return;
 	}
 	if (!_loadMoreChats) {
@@ -1560,8 +1580,7 @@ void Widget::updateControlsGeometry() {
 	right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y());
 
 	auto scrollTop = filterAreaTop + filterAreaHeight;
-	auto addToScroll = controller()->content()->contentScrollAddToY();
-	auto newScrollTop = _scroll->scrollTop() + addToScroll;
+	auto newScrollTop = _scroll->scrollTop() + _topDelta;
 	auto scrollHeight = height() - scrollTop;
 	const auto putBottomButton = [&](object_ptr<BottomButton> &button) {
 		if (button && !button->isHidden()) {
@@ -1582,7 +1601,7 @@ void Widget::updateControlsGeometry() {
 	if (scrollHeight != wasScrollHeight) {
 		controller()->floatPlayerAreaUpdated().notify(true);
 	}
-	if (addToScroll) {
+	if (_topDelta) {
 		_scroll->scrollToY(newScrollTop);
 	} else {
 		onListScroll();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 8357bb6b4..918315b5e 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -55,6 +55,11 @@ class Widget : public Window::AbstractSectionWidget, public RPCSender {
 public:
 	Widget(QWidget *parent, not_null<Window::SessionController*> controller);
 
+	// When resizing the widget with top edge moved up or down and we
+	// want to add this top movement to the scroll position, so inner
+	// content will not move.
+	void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta);
+
 	void updateDragInScroll(bool inScroll);
 
 	void searchInChat(Key chat);
@@ -233,6 +238,8 @@ private:
 	object_ptr<QTimer> _draggingScrollTimer = { nullptr };
 	int _draggingScrollDelta = 0;
 
+	int _topDelta = 0;
+
 };
 
 } // namespace Dialogs
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 5fba0d408..1bef57046 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/localstorage.h"
 #include "storage/storage_facade.h"
 #include "storage/storage_shared_media.h"
+#include "storage/storage_account.h"
 //#include "storage/storage_feed_messages.h" // #feed
 #include "support/support_helper.h"
 #include "ui/image/image.h"
@@ -336,7 +337,7 @@ void History::clearEditDraft() {
 
 void History::draftSavedToCloud() {
 	updateChatListEntry();
-	if (App::main()) App::main()->writeDrafts(this);
+	session().local().writeDrafts(this);
 }
 
 HistoryItemsList History::validateForwardDraft() {
@@ -1760,6 +1761,7 @@ void History::outboxRead(MsgId upTo) {
 		}
 	}
 	updateChatListEntry();
+	session().data().historyOutboxRead(this);
 }
 
 void History::outboxRead(not_null<const HistoryItem*> wasRead) {
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 58da4cf4a..d085af606 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -198,6 +198,12 @@ HistoryInner::HistoryInner(
 	}) | rpl::start_with_next([this](not_null<const Element*> view) {
 		mouseActionUpdate();
 	}, lifetime());
+	session().data().historyOutboxReads(
+	) | rpl::filter([=](not_null<History*> history) {
+		return (_history == history);
+	}) | rpl::start_with_next([=] {
+		update();
+	}, lifetime());
 }
 
 Main::Session &HistoryInner::session() const {
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index cf700636c..05e964f10 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -512,6 +512,21 @@ HistoryWidget::HistoryWidget(
 		}
 	}, lifetime());
 
+	session().data().webPageUpdates(
+	) | rpl::filter([=](not_null<WebPageData*> page) {
+		return (_previewData == page.get());
+	}) | rpl::start_with_next([=] {
+		updatePreview();
+	}, lifetime());
+
+	session().data().channelDifferenceTooLong(
+	) | rpl::filter([=](not_null<ChannelData*> channel) {
+		return _peer == channel.get();
+	}) | rpl::start_with_next([=] {
+		updateHistoryDownVisibility();
+		preloadHistoryIfNeeded();
+	}, lifetime());
+
 	session().data().userIsBotChanges(
 	) | rpl::filter([=](not_null<UserData*> user) {
 		return (_peer == user.get());
@@ -681,6 +696,24 @@ HistoryWidget::HistoryWidget(
 	setupShortcuts();
 }
 
+void HistoryWidget::setGeometryWithTopMoved(
+		const QRect &newGeometry,
+		int topDelta) {
+	_topDelta = topDelta;
+	bool willBeResized = (size() != newGeometry.size());
+	if (geometry() != newGeometry) {
+		auto weak = Ui::MakeWeak(this);
+		setGeometry(newGeometry);
+		if (!weak) {
+			return;
+		}
+	}
+	if (!willBeResized) {
+		resizeEvent(nullptr);
+	}
+	_topDelta = 0;
+}
+
 void HistoryWidget::refreshTabbedPanel() {
 	if (_peer && controller()->hasTabbedSelectorOwnership()) {
 		createTabbedPanel();
@@ -1230,7 +1263,7 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
 }
 
 void HistoryWidget::onCloudDraftSave() {
-	controller()->content()->saveDraftToCloud();
+	controller()->session().api().saveCurrentDraftToCloud();
 }
 
 void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) {
@@ -1735,7 +1768,7 @@ void HistoryWidget::showHistory(
 			// Removing focus from list clears selected and updates top bar.
 			setFocus();
 		}
-		controller()->content()->saveDraftToCloud();
+		controller()->session().api().saveCurrentDraftToCloud();
 		if (_migrated) {
 			_migrated->clearLocalDraft(); // use migrated draft only once
 			_migrated->clearEditDraft();
@@ -2550,7 +2583,7 @@ bool HistoryWidget::doWeReadMentions() const {
 		&& !_firstLoadRequest
 		&& !_delayedShowAtRequest
 		&& !_a_show.animating()
-		&& App::wnd()->doWeMarkAsRead();
+		&& controller()->widget()->doWeMarkAsRead();
 }
 
 void HistoryWidget::checkHistoryActivation() {
@@ -3022,7 +3055,7 @@ void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates,
 	if (auto editDraft = history->editDraft()) {
 		if (editDraft->saveRequestId == req) {
 			history->clearEditDraft();
-			controller()->content()->writeDrafts(history);
+			session().local().writeDrafts(history);
 		}
 	}
 }
@@ -5028,7 +5061,7 @@ void HistoryWidget::updateControlsGeometry() {
 		}
 	}
 
-	updateHistoryGeometry(false, false, { ScrollChangeAdd, controller()->content()->contentScrollAddToY() });
+	updateHistoryGeometry(false, false, { ScrollChangeAdd, _topDelta });
 
 	updateFieldSize();
 
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index c324894cd..95a433937 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -112,6 +112,11 @@ public:
 
 	void historyLoaded();
 
+	// When resizing the widget with top edge moved up or down and we
+	// want to add this top movement to the scroll position, so inner
+	// content will not move.
+	void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta);
+
 	void windowShown();
 	[[nodiscard]] bool doWeReadServerHistory() const;
 	[[nodiscard]] bool doWeReadMentions() const;
@@ -820,4 +825,6 @@ private:
 	object_ptr<Ui::PlainShadow> _topShadow;
 	bool _inGrab = false;
 
+	int _topDelta = 0;
+
 };
diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp
index 460995c67..0e21790ab 100644
--- a/Telegram/SourceFiles/main/main_account.cpp
+++ b/Telegram/SourceFiles/main/main_account.cpp
@@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/image/image.h"
 #include "mainwidget.h"
 #include "observer_peer.h"
+#include "api/api_updates.h"
+#include "apiwrap.h"
 #include "main/main_app_config.h"
 #include "main/main_session.h"
 #include "facades.h"
@@ -353,9 +355,11 @@ void Account::startMtp() {
 			Global::RefConnectionTypeChanged().notify();
 		}
 	});
-	_mtp->setSessionResetHandler([](MTP::ShiftedDcId shiftedDcId) {
-		if (App::main() && shiftedDcId == MTP::maindc()) {
-			App::main()->getDifference();
+	_mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) {
+		if (sessionExists()) {
+			if (shiftedDcId == session().api().instance()->mainDcId()) {
+				session().updates().getDifference();
+			}
 		}
 	});
 
diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp
index e2d1f39ca..56a455b26 100644
--- a/Telegram/SourceFiles/main/main_session.cpp
+++ b/Telegram/SourceFiles/main/main_session.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 
 #include "apiwrap.h"
+#include "api/api_updates.h"
 #include "core/application.h"
 #include "core/changelogs.h"
 #include "main/main_account.h"
@@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_user.h"
 #include "window/notifications_manager.h"
+#include "window/window_session_controller.h"
 #include "window/themes/window_theme.h"
 //#include "platform/platform_specific.h"
 #include "calls/calls_instance.h"
@@ -48,6 +50,7 @@ Session::Session(
 , _settings(std::move(settings))
 , _saveSettingsTimer([=] { local().writeSettings(); })
 , _api(std::make_unique<ApiWrap>(this))
+, _updates(std::make_unique<Api::Updates>(this))
 , _calls(std::make_unique<Calls::Instance>(this))
 , _downloader(std::make_unique<Storage::DownloadManagerMtproto>(_api.get()))
 , _uploader(std::make_unique<Storage::Uploader>(_api.get()))
@@ -176,4 +179,20 @@ void Session::saveSettingsNowIfNeeded() {
 	}
 }
 
+void Session::addWindow(not_null<Window::SessionController*> controller) {
+	_windows.emplace(controller);
+	controller->lifetime().add([=] {
+		_windows.remove(controller);
+	});
+	updates().addActiveChat(controller->activeChatChanges(
+	) | rpl::map([=](const Dialogs::Key &chat) {
+		return chat.peer();
+	}) | rpl::distinct_until_changed());
+}
+
+auto Session::windows() const
+-> const base::flat_set<not_null<Window::SessionController*>> & {
+	return _windows;
+}
+
 } // namespace Main
diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h
index 6854b8075..1e23100ce 100644
--- a/Telegram/SourceFiles/main/main_session.h
+++ b/Telegram/SourceFiles/main/main_session.h
@@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class ApiWrap;
 
+namespace Api {
+class Updates;
+} // namespace Api
+
 namespace MTP {
 class Instance;
 } // namespace MTP
@@ -39,6 +43,7 @@ namespace Window {
 namespace Notifications {
 class System;
 } // namespace Notifications
+class SessionController;
 } // namespace Window
 
 namespace Calls {
@@ -81,13 +86,16 @@ public:
 	}
 	bool validateSelf(const MTPUser &user);
 
-	[[nodiscard]] Storage::DownloadManagerMtproto &downloader() {
+	[[nodiscard]] Api::Updates &updates() const {
+		return *_updates;
+	}
+	[[nodiscard]] Storage::DownloadManagerMtproto &downloader() const {
 		return *_downloader;
 	}
-	[[nodiscard]] Storage::Uploader &uploader() {
+	[[nodiscard]] Storage::Uploader &uploader() const {
 		return *_uploader;
 	}
-	[[nodiscard]] Storage::Facade &storage() {
+	[[nodiscard]] Storage::Facade &storage() const {
 		return *_storage;
 	}
 	[[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const {
@@ -112,6 +120,10 @@ public:
 	void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
 	void saveSettingsNowIfNeeded();
 
+	void addWindow(not_null<Window::SessionController*> controller);
+	[[nodiscard]] auto windows() const
+		-> const base::flat_set<not_null<Window::SessionController*>> &;
+
 	[[nodiscard]] not_null<MTP::Instance*> mtp();
 	[[nodiscard]] ApiWrap &api() {
 		return *_api;
@@ -143,6 +155,7 @@ private:
 	base::Timer _saveSettingsTimer;
 
 	const std::unique_ptr<ApiWrap> _api;
+	const std::unique_ptr<Api::Updates> _updates;
 	const std::unique_ptr<Calls::Instance> _calls;
 	const std::unique_ptr<Storage::DownloadManagerMtproto> _downloader;
 	const std::unique_ptr<Storage::Uploader> _uploader;
@@ -162,6 +175,8 @@ private:
 
 	const std::unique_ptr<Support::Helper> _supportHelper;
 
+	base::flat_set<not_null<Window::SessionController*>> _windows;
+
 	rpl::lifetime _lifetime;
 
 };
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 664c3c8ef..88383a676 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <rpl/combine.h>
 #include <rpl/merge.h>
 #include <rpl/flatten_latest.h>
+#include "api/api_updates.h"
 #include "data/data_photo.h"
 #include "data/data_document.h"
 #include "data/data_document_media.h"
@@ -112,166 +113,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace {
 
-constexpr auto kChannelGetDifferenceLimit = 100;
-
-// 1s wait after show channel history before sending getChannelDifference.
-constexpr auto kWaitForChannelGetDifference = crl::time(1000);
-
-// If nothing is received in 1 min we ping.
-constexpr auto kNoUpdatesTimeout = 60 * 1000;
-
-// If nothing is received in 1 min when was a sleepmode we ping.
-constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000);
-
 // Send channel views each second.
 constexpr auto kSendViewsTimeout = crl::time(1000);
 
 // Cache background scaled image after 3s.
 constexpr auto kCacheBackgroundTimeout = 3000;
 
-enum class DataIsLoadedResult {
-	NotLoaded = 0,
-	FromNotLoaded = 1,
-	MentionNotLoaded = 2,
-	Ok = 3,
-};
-
-bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) {
-	return qs(data.vtype()).startsWith(qstr("AUTH_KEY_DROP_"));
-}
-
-bool HasForceLogoutNotification(const MTPUpdates &updates) {
-	const auto checkUpdate = [](const MTPUpdate &update) {
-		if (update.type() != mtpc_updateServiceNotification) {
-			return false;
-		}
-		return IsForceLogoutNotification(
-			update.c_updateServiceNotification());
-	};
-	const auto checkVector = [&](const MTPVector<MTPUpdate> &list) {
-		for (const auto &update : list.v) {
-			if (checkUpdate(update)) {
-				return true;
-			}
-		}
-		return false;
-	};
-	switch (updates.type()) {
-	case mtpc_updates:
-		return checkVector(updates.c_updates().vupdates());
-	case mtpc_updatesCombined:
-		return checkVector(updates.c_updatesCombined().vupdates());
-	case mtpc_updateShort:
-		return checkUpdate(updates.c_updateShort().vupdate());
-	}
-	return false;
-}
-
-bool ForwardedInfoDataLoaded(
-		not_null<Main::Session*> session,
-		const MTPMessageFwdHeader &header) {
-	return header.match([&](const MTPDmessageFwdHeader &data) {
-		if (const auto channelId = data.vchannel_id()) {
-			if (!session->data().channelLoaded(channelId->v)) {
-				return false;
-			}
-			if (const auto fromId = data.vfrom_id()) {
-				const auto from = session->data().user(fromId->v);
-				// Minimal loaded is fine in this case.
-				if (from->loadedStatus == PeerData::NotLoaded) {
-					return false;
-				}
-			}
-		} else if (const auto fromId = data.vfrom_id()) {
-			// Fully loaded is required in this case.
-			if (!session->data().userLoaded(fromId->v)) {
-				return false;
-			}
-		}
-		return true;
-	});
-}
-
-bool MentionUsersLoaded(
-		not_null<Main::Session*> session,
-		const MTPVector<MTPMessageEntity> &entities) {
-	for (const auto &entity : entities.v) {
-		auto type = entity.type();
-		if (type == mtpc_messageEntityMentionName) {
-			if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id().v)) {
-				return false;
-			}
-		} else if (type == mtpc_inputMessageEntityMentionName) {
-			auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id();
-			if (inputUser.type() == mtpc_inputUser) {
-				if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id().v)) {
-					return false;
-				}
-			}
-		}
-	}
-	return true;
-}
-
-DataIsLoadedResult AllDataLoadedForMessage(
-		not_null<Main::Session*> session,
-		const MTPMessage &message) {
-	return message.match([&](const MTPDmessage &message) {
-		if (const auto fromId = message.vfrom_id()) {
-			if (!message.is_post()
-				&& !session->data().userLoaded(fromId->v)) {
-				return DataIsLoadedResult::FromNotLoaded;
-			}
-		}
-		if (const auto viaBotId = message.vvia_bot_id()) {
-			if (!session->data().userLoaded(viaBotId->v)) {
-				return DataIsLoadedResult::NotLoaded;
-			}
-		}
-		if (const auto fwd = message.vfwd_from()) {
-			if (!ForwardedInfoDataLoaded(session, *fwd)) {
-				return DataIsLoadedResult::NotLoaded;
-			}
-		}
-		if (const auto entities = message.ventities()) {
-			if (!MentionUsersLoaded(session, *entities)) {
-				return DataIsLoadedResult::MentionNotLoaded;
-			}
-		}
-		return DataIsLoadedResult::Ok;
-	}, [&](const MTPDmessageService &message) {
-		if (const auto fromId = message.vfrom_id()) {
-			if (!message.is_post()
-				&& !session->data().userLoaded(fromId->v)) {
-				return DataIsLoadedResult::FromNotLoaded;
-			}
-		}
-		return message.vaction().match(
-		[&](const MTPDmessageActionChatAddUser &action) {
-			for (const MTPint &userId : action.vusers().v) {
-				if (!session->data().userLoaded(userId.v)) {
-					return DataIsLoadedResult::NotLoaded;
-				}
-			}
-			return DataIsLoadedResult::Ok;
-		}, [&](const MTPDmessageActionChatJoinedByLink &action) {
-			if (!session->data().userLoaded(action.vinviter_id().v)) {
-				return DataIsLoadedResult::NotLoaded;
-			}
-			return DataIsLoadedResult::Ok;
-		}, [&](const MTPDmessageActionChatDeleteUser &action) {
-			if (!session->data().userLoaded(action.vuser_id().v)) {
-				return DataIsLoadedResult::NotLoaded;
-			}
-			return DataIsLoadedResult::Ok;
-		}, [](const auto &) {
-			return DataIsLoadedResult::Ok;
-		});
-	}, [](const MTPDmessageEmpty &message) {
-		return DataIsLoadedResult::Ok;
-	});
-}
-
 } // namespace
 
 enum StackItemType {
@@ -385,14 +232,6 @@ MainWidget::MainWidget(
 , _dialogs(this, _controller)
 , _history(this, _controller)
 , _playerPlaylist(this, _controller)
-, _noUpdatesTimer([=] { sendPing(); })
-, _ptsWaiter(&controller->session())
-, _byPtsTimer([=] { getDifferenceByPts(); })
-, _bySeqTimer([=] { getDifference(); })
-, _byMinChannelTimer([=] { getDifference(); })
-, _onlineTimer([=] { updateOnline(); })
-, _idleFinishTimer([=] { checkIdleFinish(); })
-, _failDifferenceTimer([=] { getDifferenceAfterFail(); })
 , _cacheBackgroundTimer([=] { cacheBackground(); })
 , _viewsIncrementTimer([=] { viewsIncrement(); }) {
 	_controller->setDefaultFloatPlayerDelegate(floatPlayerDelegate());
@@ -410,7 +249,6 @@ MainWidget::MainWidget(
 			_selfUserpicView);
 	}, lifetime());
 
-	_ptsWaiter.setRequesting(true);
 	updateScrollColors();
 	setupConnectingWidget();
 
@@ -445,16 +283,6 @@ MainWidget::MainWidget(
 		[this] { updateControlsGeometry(); },
 		lifetime());
 
-	session().account().mtpUpdates(
-	) | rpl::start_with_next([=](const MTPUpdates &updates) {
-		mtpUpdateReceived(updates);
-	}, lifetime());
-
-	session().account().mtpNewSessionCreated(
-	) | rpl::start_with_next([=] {
-		mtpNewSessionCreated();
-	}, lifetime());
-
 	// MSVC BUG + REGRESSION rpl::mappers::tuple :(
 	using namespace rpl::mappers;
 	_controller->activeChatValue(
@@ -521,6 +349,8 @@ MainWidget::MainWidget(
 	}
 }
 
+MainWidget::~MainWidget() = default;
+
 Main::Session &MainWidget::session() const {
 	return _controller->session();
 }
@@ -1042,14 +872,6 @@ void MainWidget::itemEdited(not_null<HistoryItem*> item) {
 	}
 }
 
-void MainWidget::checkLastUpdate(bool afterSleep) {
-	auto n = crl::now();
-	if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? kNoUpdatesAfterSleepTimeout : kNoUpdatesTimeout)) {
-		_lastUpdateTime = n;
-		MTP::ping();
-	}
-}
-
 void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
 	using State = Media::Player::State;
 	const auto document = state.id.audio();
@@ -1298,10 +1120,6 @@ void MainWidget::dialogsCancelled() {
 	_history->activate();
 }
 
-bool MainWidget::isIdle() const {
-	return _isIdle;
-}
-
 void MainWidget::clearCachedBackground() {
 	_cachedBackground = QPixmap();
 	_cacheBackgroundTimer.cancel();
@@ -1676,10 +1494,6 @@ void MainWidget::ui_showPeerHistory(
 	} else {
 		const auto nowActivePeer = _controller->activeChatCurrent().peer();
 		if (nowActivePeer && nowActivePeer != wasActivePeer) {
-			if (const auto channel = nowActivePeer->asChannel()) {
-				channel->ptsWaitingForShortPoll(
-					kWaitForChannelGetDifference);
-			}
 			_viewsIncremented.remove(nowActivePeer);
 		}
 		if (Adaptive::OneColumn() && !_dialogs->isHidden()) {
@@ -2168,10 +1982,6 @@ void MainWidget::windowShown() {
 	_history->windowShown();
 }
 
-void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) {
-	feedUpdates(result, randomId);
-}
-
 void MainWidget::historyToDown(History *history) {
 	_history->historyToDown(history);
 }
@@ -2379,8 +2189,8 @@ void MainWidget::updateControlsGeometry() {
 			mainSectionTop,
 			dialogsWidth,
 			height() - mainSectionTop);
-		_dialogs->setGeometry(mainSectionGeometry);
-		_history->setGeometry(mainSectionGeometry);
+		_dialogs->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY);
+		_history->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY);
 		if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height());
 	} else {
 		auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0;
@@ -2395,7 +2205,7 @@ void MainWidget::updateControlsGeometry() {
 		accumulate_min(dialogsWidth, width() - st::columnMinimalWidthMain);
 		auto mainSectionWidth = width() - dialogsWidth - thirdSectionWidth;
 
-		_dialogs->setGeometryToLeft(0, 0, dialogsWidth, height());
+		_dialogs->setGeometryWithTopMoved({ 0, 0, dialogsWidth, height() }, _contentScrollAddToY);
 		_sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height());
 		if (_thirdShadow) {
 			_thirdShadow->setGeometryToLeft(
@@ -2418,7 +2228,7 @@ void MainWidget::updateControlsGeometry() {
 				dialogsWidth,
 				_callTopBarHeight + _exportTopBarHeight);
 		}
-		_history->setGeometryToLeft(dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop);
+		_history->setGeometryWithTopMoved({ dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop }, _contentScrollAddToY);
 		if (_hider) {
 			_hider->setGeometryToLeft(dialogsWidth, 0, mainSectionWidth, height());
 		}
@@ -2651,10 +2461,6 @@ void MainWidget::updateMediaPlaylistPosition(int x) {
 	}
 }
 
-int MainWidget::contentScrollAddToY() const {
-	return _contentScrollAddToY;
-}
-
 void MainWidget::returnTabbedSelector() {
 	if (!_mainSection || !_mainSection->returnTabbedSelector()) {
 		_history->returnTabbedSelector();
@@ -2789,398 +2595,6 @@ void MainWidget::searchInChat(Dialogs::Key chat) {
 	}
 }
 
-void MainWidget::feedUpdateVector(
-		const MTPVector<MTPUpdate> &updates,
-		bool skipMessageIds) {
-	for (const auto &update : updates.v) {
-		if (skipMessageIds && update.type() == mtpc_updateMessageID) {
-			continue;
-		}
-		feedUpdate(update);
-	}
-	session().data().sendHistoryChangeNotifications();
-}
-
-void MainWidget::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
-	for (const auto &update : updates.v) {
-		if (update.type() == mtpc_updateMessageID) {
-			feedUpdate(update);
-		}
-	}
-}
-
-void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) {
-	if (pts) {
-		_ptsWaiter.init(pts);
-	}
-	if (updDate < date && !_byMinChannelTimer.isActive()) {
-		updDate = date;
-	}
-	if (qts && updQts < qts) {
-		updQts = qts;
-	}
-	if (seq && seq != updSeq) {
-		updSeq = seq;
-		if (_bySeqTimer.isActive()) {
-			_bySeqTimer.cancel();
-		}
-		for (QMap<int32, MTPUpdates>::iterator i = _bySeqUpdates.begin(); i != _bySeqUpdates.end();) {
-			int32 s = i.key();
-			if (s <= seq + 1) {
-				MTPUpdates v = i.value();
-				i = _bySeqUpdates.erase(i);
-				if (s == seq + 1) {
-					return feedUpdates(v);
-				}
-			} else {
-				if (!_bySeqTimer.isActive()) {
-					_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
-				}
-				break;
-			}
-		}
-	}
-}
-
-void MainWidget::gotChannelDifference(
-		ChannelData *channel,
-		const MTPupdates_ChannelDifference &difference) {
-	_channelFailDifferenceTimeout.remove(channel);
-
-	const auto timeout = difference.match([&](const auto &data) {
-		return data.vtimeout().value_or_empty();
-	});
-	const auto isFinal = difference.match([&](const auto &data) {
-		return data.is_final();
-	});
-	difference.match([&](const MTPDupdates_channelDifferenceEmpty &data) {
-		channel->ptsInit(data.vpts().v);
-	}, [&](const MTPDupdates_channelDifferenceTooLong &data) {
-		session().data().processUsers(data.vusers());
-		session().data().processChats(data.vchats());
-		const auto history = session().data().historyLoaded(channel->id);
-		if (history) {
-			history->setNotLoadedAtBottom();
-			session().api().requestChannelRangeDifference(history);
-		}
-		data.vdialog().match([&](const MTPDdialog &data) {
-			if (const auto pts = data.vpts()) {
-				channel->ptsInit(pts->v);
-			}
-		}, [&](const MTPDdialogFolder &) {
-		});
-		session().data().applyDialogs(
-			nullptr,
-			data.vmessages().v,
-			QVector<MTPDialog>(1, data.vdialog()));
-		if (_history->peer() == channel) {
-			_history->updateHistoryDownVisibility();
-			_history->preloadHistoryIfNeeded();
-		}
-	}, [&](const MTPDupdates_channelDifference &data) {
-		feedChannelDifference(data);
-		channel->ptsInit(data.vpts().v);
-	});
-
-	channel->ptsSetRequesting(false);
-
-	if (!isFinal) {
-		MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
-		getChannelDifference(channel);
-	} else if (_controller->activeChatCurrent().peer() == channel) {
-		channel->ptsWaitingForShortPoll(timeout
-			? (timeout * crl::time(1000))
-			: kWaitForChannelGetDifference);
-	}
-}
-
-void MainWidget::feedChannelDifference(
-		const MTPDupdates_channelDifference &data) {
-	session().data().processUsers(data.vusers());
-	session().data().processChats(data.vchats());
-
-	_handlingChannelDifference = true;
-	feedMessageIds(data.vother_updates());
-	session().data().processMessages(
-		data.vnew_messages(),
-		NewMessageType::Unread);
-	feedUpdateVector(data.vother_updates(), true);
-	_handlingChannelDifference = false;
-}
-
-bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	LOG(("RPC Error in getChannelDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
-	failDifferenceStartTimerFor(channel);
-	return true;
-}
-
-void MainWidget::gotState(const MTPupdates_State &state) {
-	auto &d = state.c_updates_state();
-	updSetState(d.vpts().v, d.vdate().v, d.vqts().v, d.vseq().v);
-
-	_lastUpdateTime = crl::now();
-	_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
-	_ptsWaiter.setRequesting(false);
-
-	session().api().requestDialogs();
-	updateOnline();
-}
-
-void MainWidget::gotDifference(const MTPupdates_Difference &difference) {
-	_failDifferenceTimeout = 1;
-
-	switch (difference.type()) {
-	case mtpc_updates_differenceEmpty: {
-		auto &d = difference.c_updates_differenceEmpty();
-		updSetState(_ptsWaiter.current(), d.vdate().v, updQts, d.vseq().v);
-
-		_lastUpdateTime = crl::now();
-		_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
-
-		_ptsWaiter.setRequesting(false);
-	} break;
-	case mtpc_updates_differenceSlice: {
-		auto &d = difference.c_updates_differenceSlice();
-		feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates());
-
-		auto &s = d.vintermediate_state().c_updates_state();
-		updSetState(s.vpts().v, s.vdate().v, s.vqts().v, s.vseq().v);
-
-		_ptsWaiter.setRequesting(false);
-
-		MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
-		getDifference();
-	} break;
-	case mtpc_updates_difference: {
-		auto &d = difference.c_updates_difference();
-		feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates());
-
-		gotState(d.vstate());
-	} break;
-	case mtpc_updates_differenceTooLong: {
-		auto &d = difference.c_updates_differenceTooLong();
-		LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!"));
-	} break;
-	};
-}
-
-bool MainWidget::getDifferenceTimeChanged(
-		ChannelData *channel,
-		int32 ms,
-		ChannelGetDifferenceTime &channelCurTime,
-		crl::time &curTime) {
-	if (channel) {
-		if (ms <= 0) {
-			ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel);
-			if (i != channelCurTime.cend()) {
-				channelCurTime.erase(i);
-			} else {
-				return false;
-			}
-		} else {
-			auto when = crl::now() + ms;
-			ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel);
-			if (i != channelCurTime.cend()) {
-				if (i.value() > when) {
-					i.value() = when;
-				} else {
-					return false;
-				}
-			} else {
-				channelCurTime.insert(channel, when);
-			}
-		}
-	} else {
-		if (ms <= 0) {
-			if (curTime) {
-				curTime = 0;
-			} else {
-				return false;
-			}
-		} else {
-			auto when = crl::now() + ms;
-			if (!curTime || curTime > when) {
-				curTime = when;
-			} else {
-				return false;
-			}
-		}
-	}
-	return true;
-}
-
-void MainWidget::ptsWaiterStartTimerFor(ChannelData *channel, int32 ms) {
-	if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeByPts, _getDifferenceTimeByPts)) {
-		getDifferenceByPts();
-	}
-}
-
-void MainWidget::failDifferenceStartTimerFor(ChannelData *channel) {
-	auto &timeout = [&]() -> int32& {
-		if (!channel) {
-			return _failDifferenceTimeout;
-		}
-		const auto i = _channelFailDifferenceTimeout.find(channel);
-		return (i == _channelFailDifferenceTimeout.end())
-			? _channelFailDifferenceTimeout.insert(channel, 1).value()
-			: i.value();
-	}();
-	if (getDifferenceTimeChanged(channel, timeout * 1000, _channelGetDifferenceTimeAfterFail, _getDifferenceTimeAfterFail)) {
-		getDifferenceAfterFail();
-	}
-	if (timeout < 64) timeout *= 2;
-}
-
-bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates) {
-	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates);
-}
-
-bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update) {
-	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update);
-}
-
-bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount) {
-	return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount);
-}
-
-void MainWidget::feedDifference(
-		const MTPVector<MTPUser> &users,
-		const MTPVector<MTPChat> &chats,
-		const MTPVector<MTPMessage> &msgs,
-		const MTPVector<MTPUpdate> &other) {
-	Core::App().checkAutoLock();
-	session().data().processUsers(users);
-	session().data().processChats(chats);
-	feedMessageIds(other);
-	session().data().processMessages(msgs, NewMessageType::Unread);
-	feedUpdateVector(other, true);
-}
-
-bool MainWidget::failDifference(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) return false;
-
-	LOG(("RPC Error in getDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
-	failDifferenceStartTimerFor(nullptr);
-	return true;
-}
-
-void MainWidget::getDifferenceByPts() {
-	auto now = crl::now(), wait = crl::time(0);
-	if (_getDifferenceTimeByPts) {
-		if (_getDifferenceTimeByPts > now) {
-			wait = _getDifferenceTimeByPts - now;
-		} else {
-			getDifference();
-		}
-	}
-	for (ChannelGetDifferenceTime::iterator i = _channelGetDifferenceTimeByPts.begin(); i != _channelGetDifferenceTimeByPts.cend();) {
-		if (i.value() > now) {
-			wait = wait ? qMin(wait, i.value() - now) : (i.value() - now);
-			++i;
-		} else {
-			getChannelDifference(i.key(), ChannelDifferenceRequest::PtsGapOrShortPoll);
-			i = _channelGetDifferenceTimeByPts.erase(i);
-		}
-	}
-	if (wait) {
-		_byPtsTimer.callOnce(wait);
-	} else {
-		_byPtsTimer.cancel();
-	}
-}
-
-void MainWidget::getDifferenceAfterFail() {
-	auto now = crl::now(), wait = crl::time(0);
-	if (_getDifferenceTimeAfterFail) {
-		if (_getDifferenceTimeAfterFail > now) {
-			wait = _getDifferenceTimeAfterFail - now;
-		} else {
-			_ptsWaiter.setRequesting(false);
-			MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : ""));
-			getDifference();
-		}
-	}
-	for (auto i = _channelGetDifferenceTimeAfterFail.begin(); i != _channelGetDifferenceTimeAfterFail.cend();) {
-		if (i.value() > now) {
-			wait = wait ? qMin(wait, i.value() - now) : (i.value() - now);
-			++i;
-		} else {
-			getChannelDifference(i.key(), ChannelDifferenceRequest::AfterFail);
-			i = _channelGetDifferenceTimeAfterFail.erase(i);
-		}
-	}
-	if (wait) {
-		_failDifferenceTimer.callOnce(wait);
-	} else {
-		_failDifferenceTimer.cancel();
-	}
-}
-
-void MainWidget::getDifference() {
-	_getDifferenceTimeByPts = 0;
-
-	if (requestingDifference()) {
-		return;
-	}
-
-	_bySeqUpdates.clear();
-	_bySeqTimer.cancel();
-
-	_noUpdatesTimer.cancel();
-	_getDifferenceTimeAfterFail = 0;
-
-	_ptsWaiter.setRequesting(true);
-
-	MTP::send(
-		MTPupdates_GetDifference(
-			MTP_flags(0),
-			MTP_int(_ptsWaiter.current()),
-			MTPint(),
-			MTP_int(updDate),
-			MTP_int(updQts)),
-		rpcDone(&MainWidget::gotDifference),
-		rpcFail(&MainWidget::failDifference));
-}
-
-void MainWidget::getChannelDifference(
-		not_null<ChannelData*> channel,
-		ChannelDifferenceRequest from) {
-	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
-		_channelGetDifferenceTimeByPts.remove(channel);
-	}
-
-	if (!channel->ptsInited() || channel->ptsRequesting()) return;
-
-	if (from != ChannelDifferenceRequest::AfterFail) {
-		_channelGetDifferenceTimeAfterFail.remove(channel);
-	}
-
-	channel->ptsSetRequesting(true);
-
-	auto filter = MTP_channelMessagesFilterEmpty();
-	auto flags = MTPupdates_GetChannelDifference::Flag::f_force | 0;
-	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
-		if (!channel->ptsWaitingForSkipped()) {
-			flags = 0; // No force flag when requesting for short poll.
-		}
-	}
-	MTP::send(
-		MTPupdates_GetChannelDifference(
-			MTP_flags(flags),
-			channel->inputChannel,
-			filter,
-			MTP_int(channel->pts()),
-			MTP_int(kChannelGetDifferenceLimit)),
-		rpcDone(&MainWidget::gotChannelDifference, channel.get()),
-		rpcFail(&MainWidget::failChannelDifference, channel.get()));
-}
-
-void MainWidget::sendPing() {
-	MTP::ping();
-}
-
 void MainWidget::start() {
 	auto &api = session().api();
 	api.requestNotifySettings(MTP_inputNotifyUsers());
@@ -3190,9 +2604,6 @@ void MainWidget::start() {
 	cSetOtherOnline(0);
 	session().user()->loadUserpic();
 
-	MTP::send(MTPupdates_GetState(), rpcDone(&MainWidget::gotState));
-	update();
-
 	_started = true;
 	auto &local = session().local();
 	local.readInstalledStickers();
@@ -3493,1121 +2904,25 @@ void MainWidget::activate() {
 }
 
 bool MainWidget::isActive() const {
-	return !_isIdle && isVisible() && !_a_show.animating();
+	return isVisible()
+		&& !_a_show.animating()
+		&& !session().updates().isIdle();
 }
 
 bool MainWidget::doWeMarkAsRead() const {
 	return isActive() && !_mainSection;
 }
 
-bool MainWidget::lastWasOnline() const {
-	return _lastWasOnline;
-}
-
-crl::time MainWidget::lastSetOnline() const {
-	return _lastSetOnline;
-}
-
 int32 MainWidget::dlgsWidth() const {
 	return _dialogs->width();
 }
 
-MainWidget::~MainWidget() {
-	if (App::main() == this) {
-		_history->showHistory(0, 0);
-	}
-}
-
-void MainWidget::updateOnline(bool gotOtherOffline) {
-	crl::on_main(this, [] { Core::App().checkAutoLock(); });
-
-	bool isOnline = !App::quitting() && App::wnd()->isActive();
-	int updateIn = Global::OnlineUpdatePeriod();
-	Assert(updateIn >= 0);
-	if (isOnline) {
-		const auto idle = crl::now() - Core::App().lastNonIdleTime();
-		if (idle >= Global::OfflineIdleTimeout()) {
-			isOnline = false;
-			if (!_isIdle) {
-				_isIdle = true;
-				_idleFinishTimer.callOnce(900);
-			}
-		} else {
-			updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle));
-			Assert(updateIn >= 0);
-		}
-	}
-	auto ms = crl::now();
-	if (isOnline != _lastWasOnline
-		|| (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms)
-		|| (isOnline && gotOtherOffline)) {
-		if (_onlineRequest) {
-			MTP::cancel(_onlineRequest);
-			_onlineRequest = 0;
-		}
-
-		_lastWasOnline = isOnline;
-		_lastSetOnline = ms;
-		if (!App::quitting()) {
-			_onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline)));
-		} else {
-			_onlineRequest = MTP::send(
-				MTPaccount_UpdateStatus(MTP_bool(!isOnline)),
-				rpcDone(&MainWidget::updateStatusDone),
-				rpcFail(&MainWidget::updateStatusFail));
-		}
-
-		const auto self = session().user();
-		self->onlineTill = base::unixtime::now() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1);
-		Notify::peerUpdatedDelayed(
-			self,
-			Notify::PeerUpdate::Flag::UserOnlineChanged);
-		if (!isOnline) { // Went offline, so we need to save message draft to the cloud.
-			saveDraftToCloud();
-		}
-
-		_lastSetOnline = ms;
-	} else if (isOnline) {
-		updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms));
-		Assert(updateIn >= 0);
-	}
-	_onlineTimer.callOnce(updateIn);
-}
-
-void MainWidget::updateStatusDone(const MTPBool &result) {
-	Core::App().quitPreventFinished();
-}
-
-bool MainWidget::updateStatusFail(const RPCError &error) {
-	if (MTP::isDefaultHandledError(error)) {
-		return false;
-	}
-	Core::App().quitPreventFinished();
-	return true;
-}
-
-bool MainWidget::isQuitPrevent() {
-	if (!_lastWasOnline) {
-		return false;
-	}
-	LOG(("MainWidget prevents quit, sending offline status..."));
-	updateOnline();
-	return true;
-}
-
-void MainWidget::saveDraftToCloud() {
-	_history->saveFieldToHistoryLocalDraft();
-
-	const auto peer = _history->peer();
-	if (const auto history = session().data().historyLoaded(peer)) {
-		writeDrafts(history);
-
-		const auto localDraft = history->localDraft();
-		const auto cloudDraft = history->cloudDraft();
-		if (!Data::draftsAreEqual(localDraft, cloudDraft)
-			&& !session().supportMode()) {
-			session().api().saveDraftToCloudDelayed(history);
-		}
-	}
-}
-
 void MainWidget::applyCloudDraft(History *history) {
 	_history->applyCloudDraft(history);
 }
 
-void MainWidget::writeDrafts(History *history) {
-	Storage::MessageDraft storedLocalDraft, storedEditDraft;
-	MessageCursor localCursor, editCursor;
-	if (const auto localDraft = history->localDraft()) {
-		if (session().supportMode()
-			|| !Data::draftsAreEqual(localDraft, history->cloudDraft())) {
-			storedLocalDraft = Storage::MessageDraft{
-				localDraft->msgId,
-				localDraft->textWithTags,
-				localDraft->previewCancelled
-			};
-			localCursor = localDraft->cursor;
-		}
-	}
-	if (const auto editDraft = history->editDraft()) {
-		storedEditDraft = Storage::MessageDraft{
-			editDraft->msgId,
-			editDraft->textWithTags,
-			editDraft->previewCancelled
-		};
-		editCursor = editDraft->cursor;
-	}
-	session().local().writeDrafts(
-		history->peer->id,
-		storedLocalDraft,
-		storedEditDraft);
-	session().local().writeDraftCursors(history->peer->id, localCursor, editCursor);
-}
-
-void MainWidget::checkIdleFinish() {
-	if (crl::now() - Core::App().lastNonIdleTime()
-		< Global::OfflineIdleTimeout()) {
-		_idleFinishTimer.cancel();
-		_isIdle = false;
-		updateOnline();
-		App::wnd()->checkHistoryActivation();
-	} else {
-		_idleFinishTimer.callOnce(900);
-	}
-}
-
-void MainWidget::mtpNewSessionCreated() {
-	Core::App().checkAutoLock();
-	updSeq = 0;
-	MTP_LOG(0, ("getDifference { after new_session_created }%1"
-		).arg(cTestMode() ? " TESTMODE" : ""));
-	getDifference();
-}
-
-void MainWidget::mtpUpdateReceived(const MTPUpdates &updates) {
-	Core::App().checkAutoLock();
-	_lastUpdateTime = crl::now();
-	_noUpdatesTimer.callOnce(kNoUpdatesTimeout);
-	if (!requestingDifference()
-		|| HasForceLogoutNotification(updates)) {
-		feedUpdates(updates);
-	}
-}
-
-void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
-	switch (updates.type()) {
-	case mtpc_updates: {
-		auto &d = updates.c_updates();
-		if (d.vseq().v) {
-			if (d.vseq().v <= updSeq) {
-				return;
-			}
-			if (d.vseq().v > updSeq + 1) {
-				_bySeqUpdates.insert(d.vseq().v, updates);
-				_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
-				return;
-			}
-		}
-
-		session().data().processUsers(d.vusers());
-		session().data().processChats(d.vchats());
-		feedUpdateVector(d.vupdates());
-
-		updSetState(0, d.vdate().v, updQts, d.vseq().v);
-	} break;
-
-	case mtpc_updatesCombined: {
-		auto &d = updates.c_updatesCombined();
-		if (d.vseq_start().v) {
-			if (d.vseq_start().v <= updSeq) {
-				return;
-			}
-			if (d.vseq_start().v > updSeq + 1) {
-				_bySeqUpdates.insert(d.vseq_start().v, updates);
-				_bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
-				return;
-			}
-		}
-
-		session().data().processUsers(d.vusers());
-		session().data().processChats(d.vchats());
-		feedUpdateVector(d.vupdates());
-
-		updSetState(0, d.vdate().v, updQts, d.vseq().v);
-	} break;
-
-	case mtpc_updateShort: {
-		auto &d = updates.c_updateShort();
-		feedUpdate(d.vupdate());
-
-		updSetState(0, d.vdate().v, updQts, updSeq);
-	} break;
-
-	case mtpc_updateShortMessage: {
-		auto &d = updates.c_updateShortMessage();
-		const auto viaBotId = d.vvia_bot_id();
-		const auto entities = d.ventities();
-		const auto fwd = d.vfwd_from();
-		if (!session().data().userLoaded(d.vuser_id().v)
-			|| (viaBotId && !session().data().userLoaded(viaBotId->v))
-			|| (entities && !MentionUsersLoaded(&session(), *entities))
-			|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
-			MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
-			return getDifference();
-		}
-		if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
-			// Update date as well.
-			updSetState(0, d.vdate().v, updQts, updSeq);
-		}
-	} break;
-
-	case mtpc_updateShortChatMessage: {
-		auto &d = updates.c_updateShortChatMessage();
-		const auto noFrom = !session().data().userLoaded(d.vfrom_id().v);
-		const auto chat = session().data().chatLoaded(d.vchat_id().v);
-		const auto viaBotId = d.vvia_bot_id();
-		const auto entities = d.ventities();
-		const auto fwd = d.vfwd_from();
-		if (!chat
-			|| noFrom
-			|| (viaBotId && !session().data().userLoaded(viaBotId->v))
-			|| (entities && !MentionUsersLoaded(&session(), *entities))
-			|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
-			MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
-			if (chat && noFrom) {
-				session().api().requestFullPeer(chat);
-			}
-			return getDifference();
-		}
-		if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
-			// Update date as well.
-			updSetState(0, d.vdate().v, updQts, updSeq);
-		}
-	} break;
-
-	case mtpc_updateShortSentMessage: {
-		auto &d = updates.c_updateShortSentMessage();
-		if (!IsServerMsgId(d.vid().v)) {
-			LOG(("API Error: Bad msgId got from server: %1").arg(d.vid().v));
-		} else if (randomId) {
-			auto &owner = session().data();
-			const auto sent = owner.messageSentData(randomId);
-			const auto lookupMessage = [&] {
-				return sent.peerId
-					? owner.message(peerToChannel(sent.peerId), d.vid().v)
-					: nullptr;
-			};
-			if (const auto id = owner.messageIdByRandomId(randomId)) {
-				const auto local = owner.message(id);
-				if (local && local->isScheduled()) {
-					owner.scheduledMessages().sendNowSimpleMessage(d, local);
-				}
-			}
-			const auto wasAlready = (lookupMessage() != nullptr);
-			feedUpdate(MTP_updateMessageID(d.vid(), MTP_long(randomId))); // ignore real date
-			if (const auto item = lookupMessage()) {
-				const auto list = d.ventities();
-				if (list && !MentionUsersLoaded(&session(), *list)) {
-					session().api().requestMessageData(
-						item->history()->peer->asChannel(),
-						item->id,
-						ApiWrap::RequestMessageDataCallback());
-				}
-				item->updateSentContent({
-					sent.text,
-					Api::EntitiesFromMTP(&session(), list.value_or_empty())
-				}, d.vmedia());
-				item->contributeToSlowmode(d.vdate().v);
-				if (!wasAlready) {
-					item->indexAsNewItem();
-				}
-			}
-		}
-
-		if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) {
-			// Update date as well.
-			updSetState(0, d.vdate().v, updQts, updSeq);
-		}
-	} break;
-
-	case mtpc_updatesTooLong: {
-		MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : ""));
-		return getDifference();
-	} break;
-	}
-	session().data().sendHistoryChangeNotifications();
-}
-
-void MainWidget::feedUpdate(const MTPUpdate &update) {
-	switch (update.type()) {
-
-	// New messages.
-	case mtpc_updateNewMessage: {
-		auto &d = update.c_updateNewMessage();
-
-		const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage());
-		if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) {
-			MTP_LOG(0, ("getDifference { good - "
-				"after not all data loaded in updateNewMessage }%1"
-				).arg(cTestMode() ? " TESTMODE" : ""));
-
-			// This can be if this update was created by grouping
-			// some short message update into an updates vector.
-			return getDifference();
-		}
-
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateNewChannelMessage: {
-		auto &d = update.c_updateNewChannelMessage();
-		auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage())));
-		const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage());
-		if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) {
-			MTP_LOG(0, ("getDifference { good - "
-				"after not all data loaded in updateNewChannelMessage }%1"
-				).arg(cTestMode() ? " TESTMODE" : ""));
-
-			// Request last active supergroup participants if the 'from' user was not loaded yet.
-			// This will optimize similar getDifference() calls for almost all next messages.
-			if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) {
-				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) {
-					session().api().requestLastParticipants(channel);
-				}
-			}
-
-			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
-				_byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
-			}
-			return;
-		}
-		if (channel && !_handlingChannelDifference) {
-			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
-				return;
-			}
-			channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-		} else {
-			session().api().applyUpdateNoPtsCheck(update);
-		}
-	} break;
-
-	case mtpc_updateMessageID: {
-		const auto &d = update.c_updateMessageID();
-		const auto randomId = d.vrandom_id().v;
-		if (const auto id = session().data().messageIdByRandomId(randomId)) {
-			const auto newId = d.vid().v;
-			if (const auto local = session().data().message(id)) {
-				if (local->isScheduled()) {
-					session().data().scheduledMessages().apply(d, local);
-				} else {
-					const auto channel = id.channel;
-					const auto existing = session().data().message(
-						channel,
-						newId);
-					if (existing && !local->mainView()) {
-						const auto history = local->history();
-						local->destroy();
-						history->requestChatListMessage();
-					} else {
-						if (existing) {
-							existing->destroy();
-						}
-						local->setRealId(d.vid().v);
-					}
-				}
-			}
-			session().data().unregisterMessageRandomId(randomId);
-		}
-		session().data().unregisterMessageSentData(randomId);
-	} break;
-
-	// Message contents being read.
-	case mtpc_updateReadMessagesContents: {
-		auto &d = update.c_updateReadMessagesContents();
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateChannelReadMessagesContents: {
-		auto &d = update.c_updateChannelReadMessagesContents();
-		auto channel = session().data().channelLoaded(d.vchannel_id().v);
-		if (!channel) {
-			if (!_byMinChannelTimer.isActive()) {
-				// getDifference after timeout.
-				_byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout);
-			}
-			return;
-		}
-		auto possiblyReadMentions = base::flat_set<MsgId>();
-		for_const (auto &msgId, d.vmessages().v) {
-			if (auto item = session().data().message(channel, msgId.v)) {
-				if (item->isUnreadMedia() || item->isUnreadMention()) {
-					item->markMediaRead();
-					session().data().requestItemRepaint(item);
-				}
-			} else {
-				// Perhaps it was an unread mention!
-				possiblyReadMentions.insert(msgId.v);
-			}
-		}
-		session().api().checkForUnreadMentions(possiblyReadMentions, channel);
-	} break;
-
-	// Edited messages.
-	case mtpc_updateEditMessage: {
-		auto &d = update.c_updateEditMessage();
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateEditChannelMessage: {
-		auto &d = update.c_updateEditChannelMessage();
-		auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage())));
-
-		if (channel && !_handlingChannelDifference) {
-			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
-				return;
-			} else {
-				channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-			}
-		} else {
-			session().api().applyUpdateNoPtsCheck(update);
-		}
-	} break;
-
-	// Messages being read.
-	case mtpc_updateReadHistoryInbox: {
-		auto &d = update.c_updateReadHistoryInbox();
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateReadHistoryOutbox: {
-		auto &d = update.c_updateReadHistoryOutbox();
-		if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update)) {
-			// We could've updated the double checks.
-			// Better would be for history to be subscribed to outbox read events.
-			_history->update();
-		}
-	} break;
-
-	case mtpc_updateReadChannelInbox: {
-		const auto &d = update.c_updateReadChannelInbox();
-		const auto peer = peerFromChannel(d.vchannel_id().v);
-		if (const auto history = session().data().historyLoaded(peer)) {
-			history->applyInboxReadUpdate(
-				d.vfolder_id().value_or_empty(),
-				d.vmax_id().v,
-				d.vstill_unread_count().v,
-				d.vpts().v);
-		}
-	} break;
-
-	case mtpc_updateReadChannelOutbox: {
-		const auto &d = update.c_updateReadChannelOutbox();
-		const auto peer = peerFromChannel(d.vchannel_id().v);
-		if (const auto history = session().data().historyLoaded(peer)) {
-			history->outboxRead(d.vmax_id().v);
-			if (!requestingDifference()) {
-				if (const auto user = history->peer->asUser()) {
-					user->madeAction(base::unixtime::now());
-				}
-			}
-			if (_history->peer() && _history->peer()->id == peer) {
-				_history->update();
-			}
-		}
-	} break;
-
-	//case mtpc_updateReadFeed: { // #feed
-	//	const auto &d = update.c_updateReadFeed();
-	//	const auto feedId = d.vfeed_id().v;
-	//	if (const auto feed = session().data().feedLoaded(feedId)) {
-	//		feed->setUnreadPosition(
-	//			Data::FeedPositionFromMTP(d.vmax_position()));
-	//		if (d.vunread_count() && d.vunread_muted_count()) {
-	//			feed->setUnreadCounts(
-	//				d.vunread_count()->v,
-	//				d.vunread_muted_count()->v);
-	//		} else {
-	//			session().data().histories().requestDialogEntry(feed);
-	//		}
-	//	}
-	//} break;
-
-	case mtpc_updateDialogUnreadMark: {
-		const auto &data = update.c_updateDialogUnreadMark();
-		data.vpeer().match(
-		[&](const MTPDdialogPeer &dialog) {
-			const auto id = peerFromMTP(dialog.vpeer());
-			if (const auto history = session().data().historyLoaded(id)) {
-				history->setUnreadMark(data.is_unread());
-			}
-		}, [&](const MTPDdialogPeerFolder &dialog) {
-			const auto id = dialog.vfolder_id().v; // #TODO archive
-			//if (const auto folder = session().data().folderLoaded(id)) {
-			//	folder->setUnreadMark(data.is_unread());
-			//}
-		});
-	} break;
-
-	case mtpc_updateFolderPeers: {
-		const auto &data = update.c_updateFolderPeers();
-
-		ptsUpdateAndApply(data.vpts().v, data.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateDialogFilter:
-	case mtpc_updateDialogFilterOrder:
-	case mtpc_updateDialogFilters: {
-		session().data().chatsFilters().apply(update);
-	} break;
-
-	// Deleted messages.
-	case mtpc_updateDeleteMessages: {
-		auto &d = update.c_updateDeleteMessages();
-
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateDeleteChannelMessages: {
-		auto &d = update.c_updateDeleteChannelMessages();
-		auto channel = session().data().channelLoaded(d.vchannel_id().v);
-
-		if (channel && !_handlingChannelDifference) {
-			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
-				return;
-			}
-			channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-		} else {
-			session().api().applyUpdateNoPtsCheck(update);
-		}
-	} break;
-
-	case mtpc_updateNewScheduledMessage: {
-		const auto &d = update.c_updateNewScheduledMessage();
-		session().data().scheduledMessages().apply(d);
-	} break;
-
-	case mtpc_updateDeleteScheduledMessages: {
-		const auto &d = update.c_updateDeleteScheduledMessages();
-		session().data().scheduledMessages().apply(d);
-	} break;
-
-	case mtpc_updateWebPage: {
-		auto &d = update.c_updateWebPage();
-
-		// Update web page anyway.
-		session().data().processWebpage(d.vwebpage());
-		_history->updatePreview();
-		session().data().sendWebPageGamePollNotifications();
-
-		ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-	} break;
-
-	case mtpc_updateChannelWebPage: {
-		auto &d = update.c_updateChannelWebPage();
-
-		// Update web page anyway.
-		session().data().processWebpage(d.vwebpage());
-		_history->updatePreview();
-		session().data().sendWebPageGamePollNotifications();
-
-		auto channel = session().data().channelLoaded(d.vchannel_id().v);
-		if (channel && !_handlingChannelDifference) {
-			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
-				return;
-			} else {
-				channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
-			}
-		} else {
-			session().api().applyUpdateNoPtsCheck(update);
-		}
-	} break;
-
-	case mtpc_updateMessagePoll: {
-		session().data().applyUpdate(update.c_updateMessagePoll());
-	} break;
-
-	case mtpc_updateUserTyping: {
-		auto &d = update.c_updateUserTyping();
-		const auto userId = peerFromUser(d.vuser_id());
-		const auto history = session().data().historyLoaded(userId);
-		const auto user = session().data().userLoaded(d.vuser_id().v);
-		if (history && user) {
-			const auto when = requestingDifference() ? 0 : base::unixtime::now();
-			session().data().registerSendAction(history, user, d.vaction(), when);
-		}
-	} break;
-
-	case mtpc_updateChatUserTyping: {
-		auto &d = update.c_updateChatUserTyping();
-		const auto history = [&]() -> History* {
-			if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) {
-				return session().data().historyLoaded(chat->id);
-			} else if (const auto channel = session().data().channelLoaded(d.vchat_id().v)) {
-				return session().data().historyLoaded(channel->id);
-			}
-			return nullptr;
-		}();
-		const auto user = (d.vuser_id().v == session().userId())
-			? nullptr
-			: session().data().userLoaded(d.vuser_id().v);
-		if (history && user) {
-			const auto when = requestingDifference() ? 0 : base::unixtime::now();
-			session().data().registerSendAction(history, user, d.vaction(), when);
-		}
-	} break;
-
-	case mtpc_updateChatParticipants: {
-		session().data().applyUpdate(update.c_updateChatParticipants());
-	} break;
-
-	case mtpc_updateChatParticipantAdd: {
-		session().data().applyUpdate(update.c_updateChatParticipantAdd());
-	} break;
-
-	case mtpc_updateChatParticipantDelete: {
-		session().data().applyUpdate(update.c_updateChatParticipantDelete());
-	} break;
-
-	case mtpc_updateChatParticipantAdmin: {
-		session().data().applyUpdate(update.c_updateChatParticipantAdmin());
-	} break;
-
-	case mtpc_updateChatDefaultBannedRights: {
-		session().data().applyUpdate(update.c_updateChatDefaultBannedRights());
-	} break;
-
-	case mtpc_updateUserStatus: {
-		auto &d = update.c_updateUserStatus();
-		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
-			switch (d.vstatus().type()) {
-			case mtpc_userStatusEmpty: user->onlineTill = 0; break;
-			case mtpc_userStatusRecently:
-				if (user->onlineTill > -10) { // don't modify pseudo-online
-					user->onlineTill = -2;
-				}
-			break;
-			case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
-			case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
-			case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break;
-			case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break;
-			}
-			Notify::peerUpdatedDelayed(
-				user,
-				Notify::PeerUpdate::Flag::UserOnlineChanged);
-		}
-		if (d.vuser_id().v == session().userId()) {
-			if (d.vstatus().type() == mtpc_userStatusOffline || d.vstatus().type() == mtpc_userStatusEmpty) {
-				updateOnline(true);
-				if (d.vstatus().type() == mtpc_userStatusOffline) {
-					cSetOtherOnline(d.vstatus().c_userStatusOffline().vwas_online().v);
-				}
-			} else if (d.vstatus().type() == mtpc_userStatusOnline) {
-				cSetOtherOnline(d.vstatus().c_userStatusOnline().vexpires().v);
-			}
-		}
-	} break;
-
-	case mtpc_updateUserName: {
-		auto &d = update.c_updateUserName();
-		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
-			if (!user->isContact()) {
-				user->setName(
-					TextUtilities::SingleLine(qs(d.vfirst_name())),
-					TextUtilities::SingleLine(qs(d.vlast_name())),
-					user->nameOrPhone,
-					TextUtilities::SingleLine(qs(d.vusername())));
-			} else {
-				user->setName(
-					TextUtilities::SingleLine(user->firstName),
-					TextUtilities::SingleLine(user->lastName),
-					user->nameOrPhone,
-					TextUtilities::SingleLine(qs(d.vusername())));
-			}
-		}
-	} break;
-
-	case mtpc_updateUserPhoto: {
-		auto &d = update.c_updateUserPhoto();
-		if (auto user = session().data().userLoaded(d.vuser_id().v)) {
-			user->setPhoto(d.vphoto());
-			user->loadUserpic();
-			if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) {
-				session().storage().remove(Storage::UserPhotosRemoveAfter(
-					user->bareId(),
-					user->userpicPhotoId()));
-			} else {
-				session().storage().add(Storage::UserPhotosAddNew(
-					user->bareId(),
-					user->userpicPhotoId()));
-			}
-		}
-	} break;
-
-	case mtpc_updatePeerSettings: {
-		const auto &d = update.c_updatePeerSettings();
-		const auto peerId = peerFromMTP(d.vpeer());
-		if (const auto peer = session().data().peerLoaded(peerId)) {
-			const auto settings = d.vsettings().match([](
-					const MTPDpeerSettings &data) {
-				return data.vflags().v;
-			});
-			peer->setSettings(settings);
-		}
-	} break;
-
-	case mtpc_updateNotifySettings: {
-		auto &d = update.c_updateNotifySettings();
-		session().data().applyNotifySetting(d.vpeer(), d.vnotify_settings());
-	} break;
-
-	case mtpc_updateDcOptions: {
-		auto &d = update.c_updateDcOptions();
-		Core::App().dcOptions()->addFromList(d.vdc_options());
-	} break;
-
-	case mtpc_updateConfig: {
-		session().mtp()->requestConfig();
-	} break;
-
-	case mtpc_updateUserPhone: {
-		const auto &d = update.c_updateUserPhone();
-		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
-			const auto newPhone = qs(d.vphone());
-			if (newPhone != user->phone()) {
-				user->setPhone(newPhone);
-				user->setName(
-					user->firstName,
-					user->lastName,
-					((user->isContact()
-						|| user->isServiceUser()
-						|| user->isSelf()
-						|| user->phone().isEmpty())
-						? QString()
-						: App::formatPhone(user->phone())),
-					user->username);
-
-				Notify::peerUpdatedDelayed(
-					user,
-					Notify::PeerUpdate::Flag::UserPhoneChanged);
-			}
-		}
-	} break;
-
-	case mtpc_updateNewEncryptedMessage: {
-		auto &d = update.c_updateNewEncryptedMessage();
-	} break;
-
-	case mtpc_updateEncryptedChatTyping: {
-		auto &d = update.c_updateEncryptedChatTyping();
-	} break;
-
-	case mtpc_updateEncryption: {
-		auto &d = update.c_updateEncryption();
-	} break;
-
-	case mtpc_updateEncryptedMessagesRead: {
-		auto &d = update.c_updateEncryptedMessagesRead();
-	} break;
-
-	case mtpc_updatePhoneCall: {
-		session().calls().handleUpdate(update.c_updatePhoneCall());
-	} break;
-
-	case mtpc_updateUserBlocked: {
-		const auto &d = update.c_updateUserBlocked();
-		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
-			user->setIsBlocked(mtpIsTrue(d.vblocked()));
-		}
-	} break;
-
-	case mtpc_updateServiceNotification: {
-		const auto &d = update.c_updateServiceNotification();
-		const auto text = TextWithEntities {
-			qs(d.vmessage()),
-			Api::EntitiesFromMTP(&session(), d.ventities().v)
-		};
-		if (IsForceLogoutNotification(d)) {
-			Core::App().forceLogOut(text);
-		} else if (d.is_popup()) {
-			Ui::show(Box<InformBox>(text));
-		} else {
-			session().data().serviceNotification(text, d.vmedia());
-			session().data().checkNewAuthorization();
-		}
-	} break;
-
-	case mtpc_updatePrivacy: {
-		auto &d = update.c_updatePrivacy();
-		const auto allChatsLoaded = [&](const MTPVector<MTPint> &ids) {
-			for (const auto &chatId : ids.v) {
-				if (!session().data().chatLoaded(chatId.v)
-					&& !session().data().channelLoaded(chatId.v)) {
-					return false;
-				}
-			}
-			return true;
-		};
-		const auto allLoaded = [&] {
-			for (const auto &rule : d.vrules().v) {
-				const auto loaded = rule.match([&](
-					const MTPDprivacyValueAllowChatParticipants & data) {
-					return allChatsLoaded(data.vchats());
-				}, [&](const MTPDprivacyValueDisallowChatParticipants & data) {
-					return allChatsLoaded(data.vchats());
-				}, [](auto &&) { return true; });
-				if (!loaded) {
-					return false;
-				}
-			}
-			return true;
-		};
-		if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) {
-			if (allLoaded()) {
-				session().api().handlePrivacyChange(*key, d.vrules());
-			} else {
-				session().api().reloadPrivacy(*key);
-			}
-		}
-	} break;
-
-	case mtpc_updatePinnedDialogs: {
-		const auto &d = update.c_updatePinnedDialogs();
-		const auto folderId = d.vfolder_id().value_or_empty();
-		const auto loaded = !folderId
-			|| (session().data().folderLoaded(folderId) != nullptr);
-		const auto folder = folderId
-			? session().data().folder(folderId).get()
-			: nullptr;
-		const auto done = [&] {
-			const auto list = d.vorder();
-			if (!list) {
-				return false;
-			}
-			const auto &order = list->v;
-			const auto notLoaded = [&](const MTPDialogPeer &peer) {
-				return peer.match([&](const MTPDdialogPeer &data) {
-					return !session().data().historyLoaded(
-						peerFromMTP(data.vpeer()));
-				}, [&](const MTPDdialogPeerFolder &data) {
-					if (folderId) {
-						LOG(("API Error: "
-							"updatePinnedDialogs has nested folders."));
-						return true;
-					}
-					return !session().data().folderLoaded(data.vfolder_id().v);
-				});
-			};
-			const auto allLoaded = ranges::find_if(order, notLoaded)
-				== order.end();
-			if (!allLoaded) {
-				return false;
-			}
-			session().data().applyPinnedChats(folder, order);
-			return true;
-		}();
-		if (!done) {
-			session().api().requestPinnedDialogs(folder);
-		}
-		if (!loaded) {
-			session().data().histories().requestDialogEntry(folder);
-		}
-	} break;
-
-	case mtpc_updateDialogPinned: {
-		const auto &d = update.c_updateDialogPinned();
-		const auto folderId = d.vfolder_id().value_or_empty();
-		const auto folder = folderId
-			? session().data().folder(folderId).get()
-			: nullptr;
-		const auto done = d.vpeer().match([&](const MTPDdialogPeer &data) {
-			const auto id = peerFromMTP(data.vpeer());
-			if (const auto history = session().data().historyLoaded(id)) {
-				history->applyPinnedUpdate(d);
-				return true;
-			}
-			DEBUG_LOG(("API Error: "
-				"pinned chat not loaded for peer %1, folder: %2"
-				).arg(id
-				).arg(folderId
-				));
-			return false;
-		}, [&](const MTPDdialogPeerFolder &data) {
-			if (folderId != 0) {
-				DEBUG_LOG(("API Error: Nested folders updateDialogPinned."));
-				return false;
-			}
-			const auto id = data.vfolder_id().v;
-			if (const auto folder = session().data().folderLoaded(id)) {
-				folder->applyPinnedUpdate(d);
-				return true;
-			}
-			DEBUG_LOG(("API Error: "
-				"pinned folder not loaded for folderId %1, folder: %2"
-				).arg(id
-				).arg(folderId
-				));
-			return false;
-		});
-		if (!done) {
-			session().api().requestPinnedDialogs(folder);
-		}
-	} break;
-
-	case mtpc_updateChannel: {
-		auto &d = update.c_updateChannel();
-		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
-			channel->inviter = UserId(0);
-			if (channel->amIn()) {
-				if (channel->isMegagroup()
-					&& !channel->amCreator()
-					&& !channel->hasAdminRights()) {
-					channel->updateFullForced();
-				}
-				const auto history = channel->owner().history(channel);
-				//if (const auto feed = channel->feed()) { // #feed
-				//	feed->requestChatListMessage();
-				//	if (!feed->unreadCountKnown()) {
-				//		feed->owner().histories().requestDialogEntry(feed);
-				//	}
-				//} else {
-					history->requestChatListMessage();
-					if (!history->unreadCountKnown()) {
-						history->owner().histories().requestDialogEntry(history);
-					}
-				//}
-				if (!channel->amCreator()) {
-					session().api().requestSelfParticipant(channel);
-				}
-			}
-		}
-	} break;
-
-	case mtpc_updateChannelTooLong: {
-		const auto &d = update.c_updateChannelTooLong();
-		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
-			const auto pts = d.vpts();
-			if (!pts || channel->pts() < pts->v) {
-				getChannelDifference(channel);
-			}
-		}
-	} break;
-
-	case mtpc_updateChannelMessageViews: {
-		auto &d = update.c_updateChannelMessageViews();
-		if (auto item = session().data().message(d.vchannel_id().v, d.vid().v)) {
-			item->setViewsCount(d.vviews().v);
-		}
-	} break;
-
-	case mtpc_updateChannelAvailableMessages: {
-		auto &d = update.c_updateChannelAvailableMessages();
-		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
-			channel->setAvailableMinId(d.vavailable_min_id().v);
-			if (const auto history = session().data().historyLoaded(channel)) {
-				history->clearUpTill(d.vavailable_min_id().v);
-			}
-		}
-	} break;
-
-	// Pinned message.
-	case mtpc_updateUserPinnedMessage: {
-		const auto &d = update.c_updateUserPinnedMessage();
-		if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
-			user->setPinnedMessageId(d.vid().v);
-		}
-	} break;
-
-	case mtpc_updateChatPinnedMessage: {
-		const auto &d = update.c_updateChatPinnedMessage();
-		if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) {
-			const auto status = chat->applyUpdateVersion(d.vversion().v);
-			if (status == ChatData::UpdateStatus::Good) {
-				chat->setPinnedMessageId(d.vid().v);
-			}
-		}
-	} break;
-
-	case mtpc_updateChannelPinnedMessage: {
-		const auto &d = update.c_updateChannelPinnedMessage();
-		if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
-			channel->setPinnedMessageId(d.vid().v);
-		}
-	} break;
-
-	////// Cloud sticker sets
-	case mtpc_updateNewStickerSet: {
-		const auto &d = update.c_updateNewStickerSet();
-		session().data().stickers().newSetReceived(d.vstickerset());
-	} break;
-
-	case mtpc_updateStickerSetsOrder: {
-		auto &d = update.c_updateStickerSetsOrder();
-		if (!d.is_masks()) {
-			const auto &order = d.vorder().v;
-			const auto &sets = session().data().stickers().sets();
-			Data::StickersSetsOrder result;
-			for (const auto &item : order) {
-				if (sets.find(item.v) == sets.cend()) {
-					break;
-				}
-				result.push_back(item.v);
-			}
-			if (result.size() != session().data().stickers().setsOrder().size()
-				|| result.size() != order.size()) {
-				session().data().stickers().setLastUpdate(0);
-				session().api().updateStickers();
-			} else {
-				session().data().stickers().setsOrderRef() = std::move(result);
-				session().local().writeInstalledStickers();
-				session().data().stickers().notifyUpdated();
-			}
-		}
-	} break;
-
-	case mtpc_updateStickerSets: {
-		session().data().stickers().setLastUpdate(0);
-		session().api().updateStickers();
-	} break;
-
-	case mtpc_updateRecentStickers: {
-		session().data().stickers().setLastRecentUpdate(0);
-		session().api().updateStickers();
-	} break;
-
-	case mtpc_updateFavedStickers: {
-		session().data().stickers().setLastFavedUpdate(0);
-		session().api().updateStickers();
-	} break;
-
-	case mtpc_updateReadFeaturedStickers: {
-		// We read some of the featured stickers, perhaps not all of them.
-		// Here we don't know what featured sticker sets were read, so we
-		// request all of them once again.
-		session().data().stickers().setLastFeaturedUpdate(0);
-		session().api().updateStickers();
-	} break;
-
-	////// Cloud saved GIFs
-	case mtpc_updateSavedGifs: {
-		session().data().stickers().setLastSavedGifsUpdate(0);
-		session().api().updateStickers();
-	} break;
-
-	////// Cloud drafts
-	case mtpc_updateDraftMessage: {
-		const auto &data = update.c_updateDraftMessage();
-		const auto peerId = peerFromMTP(data.vpeer());
-		data.vdraft().match([&](const MTPDdraftMessage &data) {
-			Data::ApplyPeerCloudDraft(&session(), peerId, data);
-		}, [&](const MTPDdraftMessageEmpty &data) {
-			Data::ClearPeerCloudDraft(
-				&session(),
-				peerId,
-				data.vdate().value_or_empty());
-		});
-	} break;
-
-	////// Cloud langpacks
-	case mtpc_updateLangPack: {
-		const auto &data = update.c_updateLangPack();
-		Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference());
-	} break;
-
-	case mtpc_updateLangPackTooLong: {
-		const auto &data = update.c_updateLangPackTooLong();
-		const auto code = qs(data.vlang_code());
-		if (!code.isEmpty()) {
-			Lang::CurrentCloudManager().requestLangPackDifference(code);
-		}
-	} break;
-
-	////// Cloud themes
-	case mtpc_updateTheme: {
-		const auto &data = update.c_updateTheme();
-		session().data().cloudThemes().applyUpdate(data.vtheme());
-	} break;
-
-	}
+void MainWidget::saveFieldToHistoryLocalDraft() {
+	_history->saveFieldToHistoryLocalDraft();
 }
 
 namespace App {
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index a90a94914..c41c3acc8 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -111,6 +111,7 @@ public:
 	MainWidget(
 		QWidget *parent,
 		not_null<Window::SessionController*> controller);
+	~MainWidget();
 
 	[[nodiscard]] Main::Session &session() const;
 	[[nodiscard]] not_null<Window::SessionController*> controller() const;
@@ -118,8 +119,6 @@ public:
 	[[nodiscard]] bool isMainSectionShown() const;
 	[[nodiscard]] bool isThirdSectionShown() const;
 
-	[[nodiscard]] int contentScrollAddToY() const;
-
 	void returnTabbedSelector();
 
 	void showAnimated(const QPixmap &bgAnimCache, bool back = false);
@@ -146,10 +145,6 @@ public:
 
 	void windowShown();
 
-	void sentUpdatesReceived(uint64 randomId, const MTPUpdates &updates);
-	void sentUpdatesReceived(const MTPUpdates &updates) {
-		return sentUpdatesReceived(0, updates);
-	}
 	void historyToDown(History *hist);
 	void dialogsToUp();
 	void checkHistoryActivation();
@@ -174,16 +169,11 @@ public:
 		const std::optional<FullMsgId> &oldId);
 	bool onSendSticker(DocumentData *sticker);
 
-	void updateOnlineDisplayIn(int32 msecs);
-
 	bool isActive() const;
 	[[nodiscard]] bool doWeMarkAsRead() const;
-	bool lastWasOnline() const;
-	crl::time lastSetOnline() const;
 
-	void saveDraftToCloud();
 	void applyCloudDraft(History *history);
-	void writeDrafts(History *history);
+	void saveFieldToHistoryLocalDraft();
 
 	int32 dlgsWidth() const;
 
@@ -222,10 +212,6 @@ public:
 	void searchMessages(const QString &query, Dialogs::Key inChat);
 	void itemEdited(not_null<HistoryItem*> item);
 
-	void checkLastUpdate(bool afterSleep);
-
-	bool isIdle() const;
-
 	QPixmap cachedBackground(const QRect &forRect, int &x, int &y);
 	void updateScrollColors();
 
@@ -248,31 +234,13 @@ public:
 	void choosePeer(PeerId peerId, MsgId showAtMsgId);
 	void clearBotStartToken(PeerData *peer);
 
-	void ptsWaiterStartTimerFor(ChannelData *channel, int32 ms); // ms <= 0 - stop timer
-	void feedUpdates(const MTPUpdates &updates, uint64 randomId = 0);
-
 	void ctrlEnterSubmitUpdated();
 	void setInnerFocus();
 
 	void scheduleViewIncrement(HistoryItem *item);
 
-	void feedChannelDifference(const MTPDupdates_channelDifference &data);
-
-	// Made public for ApiWrap, while it is still here.
-	// Better would be for this to be moved to ApiWrap.
-	bool requestingDifference() const {
-		return _ptsWaiter.requesting();
-	}
-	void getDifference();
-	void updateOnline(bool gotOtherOffline = false);
-	void checkIdleFinish();
-
 	bool contentOverlapped(const QRect &globalRect);
 
-	bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates);
-	bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update);
-	bool ptsUpdateAndApply(int32 pts, int32 ptsCount);
-
 	void searchInChat(Dialogs::Key chat);
 
 	void app_sendBotCallback(
@@ -293,10 +261,6 @@ public:
 
 	void closeBothPlayers();
 
-	bool isQuitPrevent();
-
-	~MainWidget();
-
 signals:
 	void dialogsUpdated();
 
@@ -313,27 +277,7 @@ protected:
 	bool eventFilter(QObject *o, QEvent *e) override;
 
 private:
-	using ChannelGetDifferenceTime = QMap<ChannelData*, crl::time>;
-	enum class ChannelDifferenceRequest {
-		Unknown,
-		PtsGapOrShortPoll,
-		AfterFail,
-	};
-
-	struct DeleteHistoryRequest {
-		PeerData *peer;
-		bool justClearHistory;
-	};
-
-	struct DeleteAllFromUserParams {
-		ChannelData *channel;
-		UserData *from;
-	};
-
 	void viewsIncrement();
-	void sendPing();
-	void getDifferenceByPts();
-	void getDifferenceAfterFail();
 
 	void animationCallback();
 	void handleAdaptiveLayoutUpdate();
@@ -349,7 +293,6 @@ private:
 	[[nodiscard]] bool saveThirdSectionToStackBack() const;
 	[[nodiscard]] auto thirdSectionForCurrentMainSection(Dialogs::Key key)
 		-> std::unique_ptr<Window::SectionMemento>;
-	void userIsContactUpdated(not_null<UserData*> user);
 
 	void setupConnectingWidget();
 	void createPlayer();
@@ -381,28 +324,6 @@ private:
 
 	void saveSectionInStack();
 
-	void getChannelDifference(
-		not_null<ChannelData*> channel,
-		ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown);
-	void gotDifference(const MTPupdates_Difference &diff);
-	bool failDifference(const RPCError &e);
-	void feedDifference(const MTPVector<MTPUser> &users, const MTPVector<MTPChat> &chats, const MTPVector<MTPMessage> &msgs, const MTPVector<MTPUpdate> &other);
-	void gotState(const MTPupdates_State &state);
-	void updSetState(int32 pts, int32 date, int32 qts, int32 seq);
-	void gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff);
-	bool failChannelDifference(ChannelData *channel, const RPCError &err);
-	void failDifferenceStartTimerFor(ChannelData *channel);
-
-	void mtpUpdateReceived(const MTPUpdates &updates);
-	void mtpNewSessionCreated();
-	void feedUpdateVector(
-		const MTPVector<MTPUpdate> &updates,
-		bool skipMessageIds = false);
-	// Doesn't call sendHistoryChangeNotifications itself.
-	void feedMessageIds(const MTPVector<MTPUpdate> &updates);
-	// Doesn't call sendHistoryChangeNotifications itself.
-	void feedUpdate(const MTPUpdate &update);
-
 	void usernameResolveDone(QPair<MsgId, QString> msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result);
 	bool usernameResolveFail(QString name, const RPCError &error);
 
@@ -427,14 +348,9 @@ private:
 	bool floatPlayerIsVisible(not_null<HistoryItem*> item) override;
 	void floatPlayerClosed(FullMsgId itemId);
 
-	bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, crl::time &curTime);
-
 	void viewsIncrementDone(QVector<MTPint> ids, const MTPVector<MTPint> &result, mtpRequestId req);
 	bool viewsIncrementFail(const RPCError &error, mtpRequestId req);
 
-	void updateStatusDone(const MTPBool &result);
-	bool updateStatusFail(const RPCError &error);
-
 	void refreshResizeAreas();
 	template <typename MoveCallback, typename FinishCallback>
 	void createResizeArea(
@@ -498,38 +414,6 @@ private:
 	int _exportTopBarHeight = 0;
 	int _contentScrollAddToY = 0;
 
-	int32 updDate = 0;
-	int32 updQts = -1;
-	int32 updSeq = 0;
-	base::Timer _noUpdatesTimer;
-
-	PtsWaiter _ptsWaiter;
-
-	ChannelGetDifferenceTime _channelGetDifferenceTimeByPts, _channelGetDifferenceTimeAfterFail;
-	crl::time _getDifferenceTimeByPts = 0;
-	crl::time _getDifferenceTimeAfterFail = 0;
-
-	base::Timer _byPtsTimer;
-
-	QMap<int32, MTPUpdates> _bySeqUpdates;
-	base::Timer _bySeqTimer;
-
-	base::Timer _byMinChannelTimer;
-
-	mtpRequestId _onlineRequest = 0;
-	base::Timer _onlineTimer;
-	base::Timer _idleFinishTimer;
-	bool _lastWasOnline = false;
-	crl::time _lastSetOnline = 0;
-	bool _isIdle = false;
-
-	int32 _failDifferenceTimeout = 1; // growing timeout for getDifference calls, if it fails
-	QMap<ChannelData*, int32> _channelFailDifferenceTimeout; // growing timeout for getChannelDifference calls, if it fails
-	base::Timer _failDifferenceTimer;
-
-	crl::time _lastUpdateTime = 0;
-	bool _handlingChannelDifference = false;
-
 	QPixmap _cachedBackground;
 	QRect _cachedFor, _willCacheFor;
 	int _cachedX = 0;
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index 0ea02edc4..097197b18 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_account.h"
 #include "storage/localstorage.h"
 #include "apiwrap.h"
+#include "api/api_updates.h"
 #include "settings/settings_intro.h"
 #include "platform/platform_notifications_manager.h"
 #include "base/platform/base_platform_info.h"
@@ -556,9 +557,12 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) {
 
 	case QEvent::MouseMove: {
 		const auto position = static_cast<QMouseEvent*>(e)->globalPos();
-		if (_main && _main->isIdle() && _lastMousePosition != position) {
-			Core::App().updateNonIdle();
-			_main->checkIdleFinish();
+		if (_lastMousePosition != position) {
+			if (const auto controller = sessionController()) {
+				if (controller->session().updates().isIdle()) {
+					Core::App().updateNonIdle();
+				}
+			}
 		}
 		_lastMousePosition = position;
 	} break;
@@ -1007,7 +1011,9 @@ void MainWindow::sendPaths() {
 }
 
 void MainWindow::updateIsActiveHook() {
-	if (_main) _main->updateOnline();
+	if (const auto controller = sessionController()) {
+		controller->session().updates().updateOnline();
+	}
 }
 
 MainWindow::~MainWindow() {
diff --git a/Telegram/SourceFiles/mtproto/sender.h b/Telegram/SourceFiles/mtproto/sender.h
index 6d91b2914..da96ff478 100644
--- a/Telegram/SourceFiles/mtproto/sender.h
+++ b/Telegram/SourceFiles/mtproto/sender.h
@@ -271,7 +271,9 @@ public:
 
 	public:
 		void cancel() {
-			_sender->senderRequestCancel(_requestId);
+			if (_requestId) {
+				_sender->senderRequestCancel(_requestId);
+			}
 		}
 
 	private:
diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp
index 57a279eb6..18dcee73f 100644
--- a/Telegram/SourceFiles/settings/settings_codes.cpp
+++ b/Telegram/SourceFiles/settings/settings_codes.cpp
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_session_controller.h"
 #include "media/audio/media_audio_track.h"
 #include "settings/settings_common.h"
+#include "api/api_updates.h"
 #include "facades.h"
 
 namespace Settings {
@@ -87,8 +88,8 @@ auto GenerateCodes() {
 		}));
 	});
 	codes.emplace(qsl("getdifference"), [](SessionController *window) {
-		if (auto main = App::main()) {
-			main->getDifference();
+		if (window) {
+			window->session().updates().getDifference();
 		}
 	});
 	codes.emplace(qsl("loadcolors"), [](SessionController *window) {
diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp
index bb1ee9ed7..73fe9a7f7 100644
--- a/Telegram/SourceFiles/storage/storage_account.cpp
+++ b/Telegram/SourceFiles/storage/storage_account.cpp
@@ -968,6 +968,35 @@ void Account::readMtpData() {
 	applyReadContext(std::move(context));
 }
 
+void Account::writeDrafts(not_null<History*> history) {
+	Storage::MessageDraft storedLocalDraft, storedEditDraft;
+	MessageCursor localCursor, editCursor;
+	if (const auto localDraft = history->localDraft()) {
+		if (_owner->session().supportMode()
+			|| !Data::draftsAreEqual(localDraft, history->cloudDraft())) {
+			storedLocalDraft = Storage::MessageDraft{
+				localDraft->msgId,
+				localDraft->textWithTags,
+				localDraft->previewCancelled
+			};
+			localCursor = localDraft->cursor;
+		}
+	}
+	if (const auto editDraft = history->editDraft()) {
+		storedEditDraft = Storage::MessageDraft{
+			editDraft->msgId,
+			editDraft->textWithTags,
+			editDraft->previewCancelled
+		};
+		editCursor = editDraft->cursor;
+	}
+	writeDrafts(
+		history->peer->id,
+		storedLocalDraft,
+		storedEditDraft);
+	writeDraftCursors(history->peer->id, localCursor, editCursor);
+}
+
 void Account::writeDrafts(
 		const PeerId &peer,
 		const MessageDraft &localDraft,
diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h
index 25b40d665..2473b04f0 100644
--- a/Telegram/SourceFiles/storage/storage_account.h
+++ b/Telegram/SourceFiles/storage/storage_account.h
@@ -71,6 +71,7 @@ public:
 	void writeBackground(const Data::WallPaper &paper, const QImage &image);
 	bool readBackground();
 
+	void writeDrafts(not_null<History*> history);
 	void writeDrafts(
 		const PeerId &peer,
 		const MessageDraft &localDraft,
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index ac06bddb5..c00167259 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_session_controller.h"
 #include "core/application.h"
 #include "mainwindow.h"
-#include "mainwidget.h"
+#include "api/api_updates.h"
 #include "apiwrap.h"
 #include "main/main_session.h"
 #include "facades.h"
@@ -120,9 +120,10 @@ void System::schedule(not_null<HistoryItem*> item) {
 	auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100;
 	const auto t = base::unixtime::now();
 	const auto ms = crl::now();
-	const bool isOnline = App::main()->lastWasOnline();
+	const auto &updates = history->session().updates();
+	const bool isOnline = updates.lastWasOnline();
 	const auto otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL);
-	const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL);
+	const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - updates.lastSetOnline()) > t * 1000LL);
 	if (!isOnline && otherNotOld && otherLaterThanMe) {
 		delay = Global::NotifyCloudDelay();
 	} else if (cOtherOnline() >= t) {
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index bf2bf67dc..1a9861260 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -144,6 +144,8 @@ SessionController::SessionController(
 			refreshFiltersMenu();
 		});
 	}, session->lifetime());
+
+	session->addWindow(this);
 }
 
 not_null<::MainWindow*> SessionController::widget() const {