diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 9120f9caf..b17b93b24 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -1896,43 +1896,6 @@ void ApiWrap::unblockParticipant(
 	_kickRequests.emplace(kick, requestId);
 }
 
-void ApiWrap::saveDefaultRestrictions(
-		not_null<PeerData*> peer,
-		const MTPChatBannedRights &rights,
-		Fn<void(bool)> callback) {
-	if (_defaultRestrictionsRequests.contains(peer)) {
-		return;
-	}
-	const auto requestId = request(MTPmessages_EditChatDefaultBannedRights(
-		peer->input,
-		rights
-	)).done([=](const MTPUpdates &result) {
-		_defaultRestrictionsRequests.erase(peer);
-		applyUpdates(result);
-		if (callback) {
-			callback(true);
-		}
-	}).fail([=](const RPCError &error) {
-		_defaultRestrictionsRequests.erase(peer);
-		if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
-			if (const auto chat = peer->asChat()) {
-				chat->setDefaultRestrictions(rights);
-			} else if (const auto channel = peer->asChannel()) {
-				channel->setDefaultRestrictions(rights);
-			} else {
-				Unexpected("Peer in ApiWrap::saveDefaultRestrictions.");
-			}
-			if (callback) {
-				callback(true);
-			}
-			return;
-		}
-		if (callback) {
-			callback(false);
-		}
-	}).send();
-}
-
 void ApiWrap::deleteAllFromUser(
 		not_null<ChannelData*> channel,
 		not_null<UserData*> from) {
@@ -2683,6 +2646,22 @@ void ApiWrap::checkQuitPreventFinished() {
 	}
 }
 
+void ApiWrap::registerModifyRequest(
+		const QString &key,
+		mtpRequestId requestId) {
+	const auto i = _modifyRequests.find(key);
+	if (i != end(_modifyRequests)) {
+		request(i->second).cancel();
+		i->second = requestId;
+	} else {
+		_modifyRequests.emplace(key, requestId);
+	}
+}
+
+void ApiWrap::clearModifyRequest(const QString &key) {
+	_modifyRequests.remove(key);
+}
+
 void ApiWrap::applyNotifySettings(
 		MTPInputNotifyPeer notifyPeer,
 		const MTPPeerNotifySettings &settings) {
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 9c6089517..a846a3190 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -46,6 +46,21 @@ struct CloudPasswordState;
 } // namespace Core
 
 namespace Api {
+namespace details {
+
+inline QString ToString(const QString &value) {
+	return value;
+}
+
+inline QString ToString(int32 value) {
+	return QString::number(value);
+}
+
+inline QString ToString(uint64 value) {
+	return QString::number(value);
+}
+
+} // namespace details
 
 template <typename IntRange>
 inline int32 CountHash(IntRange &&range) {
@@ -56,6 +71,24 @@ inline int32 CountHash(IntRange &&range) {
 	return int32(acc & 0x7FFFFFFF);
 }
 
+template <
+	typename ...Types,
+	typename = std::enable_if_t<(sizeof...(Types) > 0)>>
+QString RequestKey(Types &&...values) {
+	const auto strings = { details::ToString(values)... };
+	if (strings.size() == 1) {
+		return *strings.begin();
+	}
+
+	auto result = QString();
+	result.reserve(
+		ranges::accumulate(strings, 0, ranges::plus(), &QString::size));
+	for (const auto &string : strings) {
+		result.append(string);
+	}
+	return result;
+}
+
 } // namespace Api
 
 class ApiWrap : public MTP::Sender, private base::Subscriber {
@@ -103,7 +136,13 @@ public:
 
 	AuthSession &session() const;
 
-	void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
+	void applyUpdates(
+		const MTPUpdates &updates,
+		uint64 sentMessageRandomId = 0);
+
+	void registerModifyRequest(const QString &key, mtpRequestId requestId);
+	void clearModifyRequest(const QString &key);
+
 	void applyNotifySettings(
 		MTPInputNotifyPeer peer,
 		const MTPPeerNotifySettings &settings);
@@ -214,10 +253,6 @@ public:
 	void deleteAllFromUser(
 		not_null<ChannelData*> channel,
 		not_null<UserData*> from);
-	void saveDefaultRestrictions(
-		not_null<PeerData*> peer,
-		const MTPChatBannedRights &rights,
-		Fn<void(bool)> callback = nullptr);
 
 	void requestWebPageDelayed(WebPageData *page);
 	void clearWebPageRequest(WebPageData *page);
@@ -683,6 +718,8 @@ private:
 
 	not_null<AuthSession*> _session;
 
+	base::flat_map<QString, int> _modifyRequests;
+
 	MessageDataRequests _messageDataRequests;
 	QMap<ChannelData*, MessageDataRequests> _channelMessageDataRequests;
 	SingleQueuedInvokation _messageDataResolveDelayed;
@@ -710,10 +747,6 @@ private:
 		not_null<UserData*>>;
 	base::flat_map<KickRequest, mtpRequestId> _kickRequests;
 
-	base::flat_map<
-		not_null<PeerData*>,
-		mtpRequestId> _defaultRestrictionsRequests;
-
 	base::flat_set<not_null<ChannelData*>> _selfParticipantRequests;
 
 	base::flat_map<
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index b4afc728b..56af4bf24 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -130,21 +130,104 @@ void AddButtonDelete(
 		nullptr));
 }
 
+void SaveDefaultRestrictions(
+		not_null<PeerData*> peer,
+		MTPChatBannedRights rights,
+		Fn<void()> done) {
+	const auto api = &peer->session().api();
+	const auto key = Api::RequestKey("default_restrictions", peer->id);
+
+	const auto requestId = api->request(
+		MTPmessages_EditChatDefaultBannedRights(
+			peer->input,
+			rights)
+	).done([=](const MTPUpdates &result) {
+		api->clearModifyRequest(key);
+		api->applyUpdates(result);
+		done();
+	}).fail([=](const RPCError &error) {
+		api->clearModifyRequest(key);
+		if (error.type() != qstr("CHAT_NOT_MODIFIED")) {
+			return;
+		}
+		if (const auto chat = peer->asChat()) {
+			chat->setDefaultRestrictions(rights);
+		} else if (const auto channel = peer->asChannel()) {
+			channel->setDefaultRestrictions(rights);
+		} else {
+			Unexpected("Peer in ApiWrap::saveDefaultRestrictions.");
+		}
+		done();
+	}).send();
+
+	api->registerModifyRequest(key, requestId);
+}
+
+void SaveSlowmodeSeconds(
+		not_null<ChannelData*> channel,
+		int seconds,
+		Fn<void()> done) {
+	const auto api = &channel->session().api();
+	const auto key = Api::RequestKey("slowmode_seconds", channel->id);
+
+	const auto requestId = api->request(MTPchannels_ToggleSlowMode(
+		channel->inputChannel,
+		MTP_int(seconds)
+	)).done([=](const MTPUpdates &result) {
+		api->clearModifyRequest(key);
+		api->applyUpdates(result);
+		channel->setSlowmodeSeconds(seconds);
+		done();
+	}).fail([=](const RPCError &error) {
+		api->clearModifyRequest(key);
+		if (error.type() != qstr("CHAT_NOT_MODIFIED")) {
+			return;
+		}
+		channel->setSlowmodeSeconds(seconds);
+		done();
+	}).send();
+
+	api->registerModifyRequest(key, requestId);
+}
+
 void ShowEditPermissions(not_null<PeerData*> peer) {
 	const auto box = Ui::show(
 		Box<EditPeerPermissionsBox>(peer),
 		LayerOption::KeepOther);
+	const auto saving = box->lifetime().make_state<int>(0);
+	const auto save = [=](
+			not_null<PeerData*> peer,
+			EditPeerPermissionsBox::Result result) {
+		Expects(result.slowmodeSeconds == 0 || peer->isChannel());
+
+		const auto close = crl::guard(box, [=] { box->closeBox(); });
+		SaveDefaultRestrictions(
+			peer,
+			MTP_chatBannedRights(MTP_flags(result.rights), MTP_int(0)),
+			close);
+		if (const auto channel = peer->asChannel()) {
+			SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
+		}
+	};
 	box->saveEvents(
-	) | rpl::start_with_next([=](MTPDchatBannedRights::Flags restrictions) {
-		const auto callback = crl::guard(box, [=](bool success) {
-			if (success) {
-				box->closeBox();
-			}
+	) | rpl::start_with_next([=](EditPeerPermissionsBox::Result result) {
+		if (*saving) {
+			return;
+		}
+		*saving = true;
+
+		const auto saveFor = peer->migrateToOrMe();
+		const auto chat = saveFor->asChat();
+		if (!result.slowmodeSeconds || !chat) {
+			save(saveFor, result);
+			return;
+		}
+		const auto api = &peer->session().api();
+		api->migrateChat(chat, [=](not_null<ChannelData*> channel) {
+			save(channel, result);
+		}, [=](const RPCError &error) {
+			*saving = false;
 		});
-		peer->session().api().saveDefaultRestrictions(
-			peer->migrateToOrMe(),
-			MTP_chatBannedRights(MTP_flags(restrictions), MTP_int(0)),
-			callback);
 	}, box->lifetime());
 }
 
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
index 596cd71d5..dd20ddffa 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
@@ -308,8 +308,7 @@ EditPeerPermissionsBox::EditPeerPermissionsBox(
 : _peer(peer->migrateToOrMe()) {
 }
 
-auto EditPeerPermissionsBox::saveEvents() const
--> rpl::producer<MTPDchatBannedRights::Flags> {
+auto EditPeerPermissionsBox::saveEvents() const -> rpl::producer<Result> {
 	Expects(_save != nullptr);
 
 	return _save->clicks() | rpl::map(_value);
@@ -360,28 +359,31 @@ void EditPeerPermissionsBox::prepare() {
 
 	inner->add(std::move(checkboxes));
 
-	addSlowmodeSlider(inner);
+	const auto getSlowmodeSeconds = addSlowmodeSlider(inner);
 	addBannedButtons(inner);
 
-	_value = getRestrictions;
+	_value = [=, rights = getRestrictions]() -> Result {
+		return { rights(), getSlowmodeSeconds() };
+	};
 	_save = addButton(tr::lng_settings_save());
 	addButton(tr::lng_cancel(), [=] { closeBox(); });
 
 	setDimensionsToContent(st::boxWidth, inner);
 }
 
-void EditPeerPermissionsBox::addSlowmodeSlider(
+Fn<int()> EditPeerPermissionsBox::addSlowmodeSlider(
 		not_null<Ui::VerticalLayout*> container) {
 	using namespace rpl::mappers;
 
 	if (const auto chat = _peer->asChat()) {
 		if (!chat->amCreator()) {
-			return;
+			return [] { return 0; };
 		}
 	}
 	const auto channel = _peer->asChannel();
 	auto &lifetime = container->lifetime();
-	const auto secondsCount = lifetime.make_state<rpl::variable<int>>(0);
+	const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
+		channel ? channel->slowmodeSeconds() : 0);
 
 	container->add(
 		object_ptr<BoxContentDivider>(container),
@@ -455,6 +457,8 @@ void EditPeerPermissionsBox::addSlowmodeSlider(
 				st::boxDividerLabel),
 			st::proxyAboutPadding),
 		style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
+
+	return [=] { return secondsCount->current(); };
 }
 
 void EditPeerPermissionsBox::addSlowmodeLabels(
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h
index 79fa3e017..c62a28a56 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h
@@ -19,19 +19,24 @@ class EditPeerPermissionsBox : public BoxContent {
 public:
 	EditPeerPermissionsBox(QWidget*, not_null<PeerData*> peer);
 
-	rpl::producer<MTPDchatBannedRights::Flags> saveEvents() const;
+	struct Result {
+		MTPDchatBannedRights::Flags rights;
+		int slowmodeSeconds = 0;
+	};
+
+	rpl::producer<Result> saveEvents() const;
 
 protected:
 	void prepare() override;
 
 private:
-	void addSlowmodeSlider(not_null<Ui::VerticalLayout*> container);
+	Fn<int()> addSlowmodeSlider(not_null<Ui::VerticalLayout*> container);
 	void addSlowmodeLabels(not_null<Ui::VerticalLayout*> container);
 	void addBannedButtons(not_null<Ui::VerticalLayout*> container);
 
 	not_null<PeerData*> _peer;
 	Ui::RoundButton *_save = nullptr;
-	Fn<MTPDchatBannedRights::Flags()> _value;
+	Fn<Result()> _value;
 
 };
 
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index b13f80ace..0919a12d5 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_folder.h"
 #include "data/data_location.h"
+#include "base/unixtime.h"
 #include "history/history.h"
 #include "observer_peer.h"
 #include "auth_session.h"
@@ -580,6 +581,31 @@ void ChannelData::setMigrateFromChat(ChatData *chat) {
 	}
 }
 
+int ChannelData::slowmodeSeconds() const {
+	return _slowmodeSeconds;
+}
+
+void ChannelData::setSlowmodeSeconds(int seconds) {
+	if (_slowmodeSeconds == seconds) {
+		return;
+	}
+	_slowmodeSeconds = seconds;
+	Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelSlowmode);
+}
+
+TimeId ChannelData::slowmodeLastMessage() const {
+	return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage;
+}
+
+void ChannelData::setSlowmodeLastMessage(TimeId when) {
+	const auto time = when ? when : base::unixtime::now();
+	if (_slowmodeLastMessage == time) {
+		return;
+	}
+	_slowmodeLastMessage = time;
+	Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelSlowmode);
+}
+
 namespace Data {
 
 void ApplyMigration(
@@ -630,6 +656,13 @@ void ApplyChannelUpdate(
 	channel->setAdminsCount(update.vadmins_count().value_or_empty());
 	channel->setRestrictedCount(update.vbanned_count().value_or_empty());
 	channel->setKickedCount(update.vkicked_count().value_or_empty());
+	channel->setSlowmodeSeconds(update.vslowmode_seconds().value_or_empty());
+	if (const auto next = update.vslowmode_next_send_date()) {
+		if (next->v > base::unixtime::now()) {
+			channel->growSlowmodeLastMessage(
+				next->v - channel->slowmodeSeconds());
+		}
+	}
 	channel->setInviteLink(update.vexported_invite().match([&](
 			const MTPDchatInviteExported &data) {
 		return qs(data.vlink());
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index f225a0fe5..8ac00face 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -104,7 +104,9 @@ public:
 		| MTPDchannelFull::Flag::f_can_view_participants
 		| MTPDchannelFull::Flag::f_can_set_username
 		| MTPDchannelFull::Flag::f_can_set_stickers
-		| MTPDchannelFull::Flag::f_location;
+		| MTPDchannelFull::Flag::f_location
+		| MTPDchannelFull::Flag::f_slowmode_seconds
+		| MTPDchannelFull::Flag::f_slowmode_next_send_date;
 	using FullFlags = Data::Flags<
 		MTPDchannelFull::Flags,
 		kEssentialFullFlags>;
@@ -132,10 +134,10 @@ public:
 	void removeFlags(MTPDchannel::Flags which) {
 		_flags.remove(which);
 	}
-	auto flags() const {
+	[[nodiscard]] auto flags() const {
 		return _flags.current();
 	}
-	auto flagsValue() const {
+	[[nodiscard]] auto flagsValue() const {
 		return _flags.value();
 	}
 
@@ -148,58 +150,58 @@ public:
 	void removeFullFlags(MTPDchannelFull::Flags which) {
 		_fullFlags.remove(which);
 	}
-	auto fullFlags() const {
+	[[nodiscard]] auto fullFlags() const {
 		return _fullFlags.current();
 	}
-	auto fullFlagsValue() const {
+	[[nodiscard]] auto fullFlagsValue() const {
 		return _fullFlags.value();
 	}
 
-	int membersCount() const {
+	[[nodiscard]] int membersCount() const {
 		return std::max(_membersCount, 1);
 	}
 	void setMembersCount(int newMembersCount);
-	bool membersCountKnown() const {
+	[[nodiscard]] bool membersCountKnown() const {
 		return (_membersCount >= 0);
 	}
 
-	int adminsCount() const {
+	[[nodiscard]] int adminsCount() const {
 		return _adminsCount;
 	}
 	void setAdminsCount(int newAdminsCount);
 
-	int restrictedCount() const {
+	[[nodiscard]] int restrictedCount() const {
 		return _restrictedCount;
 	}
 	void setRestrictedCount(int newRestrictedCount);
 
-	int kickedCount() const {
+	[[nodiscard]] int kickedCount() const {
 		return _kickedCount;
 	}
 	void setKickedCount(int newKickedCount);
 
-	bool haveLeft() const {
+	[[nodiscard]] bool haveLeft() const {
 		return flags() & MTPDchannel::Flag::f_left;
 	}
-	bool amIn() const {
+	[[nodiscard]] bool amIn() const {
 		return !isForbidden() && !haveLeft();
 	}
-	bool addsSignature() const {
+	[[nodiscard]] bool addsSignature() const {
 		return flags() & MTPDchannel::Flag::f_signatures;
 	}
-	bool isForbidden() const {
+	[[nodiscard]] bool isForbidden() const {
 		return flags() & MTPDchannel_ClientFlag::f_forbidden;
 	}
-	bool isVerified() const {
+	[[nodiscard]] bool isVerified() const {
 		return flags() & MTPDchannel::Flag::f_verified;
 	}
-	bool isScam() const {
+	[[nodiscard]] bool isScam() const {
 		return flags() & MTPDchannel::Flag::f_scam;
 	}
 
 	static MTPChatBannedRights KickedRestrictedRights();
 	static constexpr auto kRestrictUntilForever = TimeId(INT_MAX);
-	static bool IsRestrictedForever(TimeId until) {
+	[[nodiscard]] static bool IsRestrictedForever(TimeId until) {
 		return !until || (until == kRestrictUntilForever);
 	}
 	void applyEditAdmin(
@@ -213,9 +215,9 @@ public:
 
 	void markForbidden();
 
-	bool isGroupAdmin(not_null<UserData*> user) const;
+	[[nodiscard]] bool isGroupAdmin(not_null<UserData*> user) const;
 
-	bool lastParticipantsCountOutdated() const {
+	[[nodiscard]] bool lastParticipantsCountOutdated() const {
 		if (!mgInfo
 			|| !(mgInfo->lastParticipantsStatus
 				& MegagroupInfo::LastParticipantsCountOutdated)) {
@@ -228,96 +230,96 @@ public:
 		}
 		return true;
 	}
-	bool isMegagroup() const {
+	[[nodiscard]] bool isMegagroup() const {
 		return flags() & MTPDchannel::Flag::f_megagroup;
 	}
-	bool isBroadcast() const {
+	[[nodiscard]] bool isBroadcast() const {
 		return flags() & MTPDchannel::Flag::f_broadcast;
 	}
-	bool hasUsername() const {
+	[[nodiscard]] bool hasUsername() const {
 		return flags() & MTPDchannel::Flag::f_username;
 	}
-	bool hasLocation() const {
+	[[nodiscard]] bool hasLocation() const {
 		return fullFlags() & MTPDchannelFull::Flag::f_location;
 	}
-	bool isPublic() const {
+	[[nodiscard]] bool isPublic() const {
 		return hasUsername() || hasLocation();
 	}
-	bool amCreator() const {
+	[[nodiscard]] bool amCreator() const {
 		return flags() & MTPDchannel::Flag::f_creator;
 	}
 
-	auto adminRights() const {
+	[[nodiscard]] auto adminRights() const {
 		return _adminRights.current();
 	}
-	auto adminRightsValue() const {
+	[[nodiscard]] auto adminRightsValue() const {
 		return _adminRights.value();
 	}
 	void setAdminRights(const MTPChatAdminRights &rights);
-	bool hasAdminRights() const {
+	[[nodiscard]] bool hasAdminRights() const {
 		return (adminRights() != 0);
 	}
 
-	auto restrictions() const {
+	[[nodiscard]] auto restrictions() const {
 		return _restrictions.current();
 	}
-	auto restrictionsValue() const {
+	[[nodiscard]] auto restrictionsValue() const {
 		return _restrictions.value();
 	}
-	TimeId restrictedUntil() const {
+	[[nodiscard]] TimeId restrictedUntil() const {
 		return _restrictedUntil;
 	}
 	void setRestrictions(const MTPChatBannedRights &rights);
-	bool hasRestrictions() const {
+	[[nodiscard]] bool hasRestrictions() const {
 		return (restrictions() != 0);
 	}
-	bool hasRestrictions(TimeId now) const {
+	[[nodiscard]] bool hasRestrictions(TimeId now) const {
 		return hasRestrictions()
 			&& (restrictedUntil() > now);
 	}
 
-	auto defaultRestrictions() const {
+	[[nodiscard]] auto defaultRestrictions() const {
 		return _defaultRestrictions.current();
 	}
-	auto defaultRestrictionsValue() const {
+	[[nodiscard]] auto defaultRestrictionsValue() const {
 		return _defaultRestrictions.value();
 	}
 	void setDefaultRestrictions(const MTPChatBannedRights &rights);
 
 	// Like in ChatData.
-	bool canWrite() const;
-	bool canEditInformation() const;
-	bool canEditPermissions() const;
-	bool canEditUsername() const;
-	bool canEditPreHistoryHidden() const;
-	bool canAddMembers() const;
-	bool canAddAdmins() const;
-	bool canBanMembers() const;
-	bool canSendPolls() const;
-	bool anyoneCanAddMembers() const;
+	[[nodiscard]] bool canWrite() const;
+	[[nodiscard]] bool canEditInformation() const;
+	[[nodiscard]] bool canEditPermissions() const;
+	[[nodiscard]] bool canEditUsername() const;
+	[[nodiscard]] bool canEditPreHistoryHidden() const;
+	[[nodiscard]] bool canAddMembers() const;
+	[[nodiscard]] bool canAddAdmins() const;
+	[[nodiscard]] bool canBanMembers() const;
+	[[nodiscard]] bool canSendPolls() const;
+	[[nodiscard]] bool anyoneCanAddMembers() const;
 
-	bool canEditMessages() const;
-	bool canDeleteMessages() const;
-	bool hiddenPreHistory() const;
-	bool canPublish() const;
-	bool canViewMembers() const;
-	bool canViewAdmins() const;
-	bool canViewBanned() const;
-	bool canEditSignatures() const;
-	bool canEditStickers() const;
-	bool canDelete() const;
-	bool canEditAdmin(not_null<UserData*> user) const;
-	bool canRestrictUser(not_null<UserData*> user) const;
+	[[nodiscard]] bool canEditMessages() const;
+	[[nodiscard]] bool canDeleteMessages() const;
+	[[nodiscard]] bool hiddenPreHistory() const;
+	[[nodiscard]] bool canPublish() const;
+	[[nodiscard]] bool canViewMembers() const;
+	[[nodiscard]] bool canViewAdmins() const;
+	[[nodiscard]] bool canViewBanned() const;
+	[[nodiscard]] bool canEditSignatures() const;
+	[[nodiscard]] bool canEditStickers() const;
+	[[nodiscard]] bool canDelete() const;
+	[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
+	[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
 
 	void setInviteLink(const QString &newInviteLink);
-	QString inviteLink() const;
-	bool canHaveInviteLink() const;
+	[[nodiscard]] QString inviteLink() const;
+	[[nodiscard]] bool canHaveInviteLink() const;
 
 	void setLocation(const MTPChannelLocation &data);
-	const ChannelLocation *getLocation() const;
+	[[nodiscard]] const ChannelLocation *getLocation() const;
 
 	void setLinkedChat(ChannelData *linked);
-	ChannelData *linkedChat() const;
+	[[nodiscard]] ChannelData *linkedChat() const;
 
 	void ptsInit(int32 pts) {
 		_ptsWaiter.init(pts);
@@ -335,37 +337,38 @@ public:
 		return _ptsWaiter.updateAndApply(this, pts, count, update);
 	}
 	bool ptsUpdateAndApply(
-		int32 pts,
-		int32 count,
-		const MTPUpdates &updates) {
+			int32 pts,
+			int32 count,
+			const MTPUpdates &updates) {
 		return _ptsWaiter.updateAndApply(this, pts, count, updates);
 	}
-	int32 pts() const {
+	[[nodiscard]] int32 pts() const {
 		return _ptsWaiter.current();
 	}
-	bool ptsInited() const {
+	[[nodiscard]] bool ptsInited() const {
 		return _ptsWaiter.inited();
 	}
-	bool ptsRequesting() const {
+	[[nodiscard]] bool ptsRequesting() const {
 		return _ptsWaiter.requesting();
 	}
 	void ptsSetRequesting(bool isRequesting) {
 		return _ptsWaiter.setRequesting(isRequesting);
 	}
-	void ptsWaitingForShortPoll(int32 ms) { // < 0 - not waiting
+	// < 0 - not waiting
+	void ptsWaitingForShortPoll(int32 ms) {
 		return _ptsWaiter.setWaitingForShortPoll(this, ms);
 	}
-	bool ptsWaitingForSkipped() const {
+	[[nodiscard]] bool ptsWaitingForSkipped() const {
 		return _ptsWaiter.waitingForSkipped();
 	}
-	bool ptsWaitingForShortPoll() const {
+	[[nodiscard]] bool ptsWaitingForShortPoll() const {
 		return _ptsWaiter.waitingForShortPoll();
 	}
 
-	QString unavailableReason() const override;
+	[[nodiscard]] QString unavailableReason() const override;
 	void setUnavailableReason(const QString &reason);
 
-	MsgId availableMinId() const {
+	[[nodiscard]] MsgId availableMinId() const {
 		return _availableMinId;
 	}
 	void setAvailableMinId(MsgId availableMinId);
@@ -383,9 +386,14 @@ public:
 	}
 	UpdateStatus applyUpdateVersion(int version);
 
-	ChatData *getMigrateFromChat() const;
+	[[nodiscard]] ChatData *getMigrateFromChat() const;
 	void setMigrateFromChat(ChatData *chat);
 
+	[[nodiscard]] int slowmodeSeconds() const;
+	void setSlowmodeSeconds(int seconds);
+	[[nodiscard]] TimeId slowmodeLastMessage() const;
+	void setSlowmodeLastMessage(TimeId when = 0);
+
 	// Still public data members.
 	uint64 access = 0;
 
@@ -396,7 +404,8 @@ public:
 	int32 date = 0;
 	std::unique_ptr<MegagroupInfo> mgInfo;
 
-	UserId inviter = 0; // > 0 - user who invited me to channel, < 0 - not in channel
+	// > 0 - user who invited me to channel, < 0 - not in channel.
+	UserId inviter = 0;
 	TimeId inviteDate = 0;
 
 private:
@@ -423,6 +432,9 @@ private:
 	QString _inviteLink;
 	ChannelData *_linkedChat = nullptr;
 
+	int _slowmodeSeconds = 0;
+	TimeId _slowmodeLastMessage = 0;
+
 	rpl::lifetime _lifetime;
 
 };
diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h
index 4eaf46c3c..231342793 100644
--- a/Telegram/SourceFiles/observer_peer.h
+++ b/Telegram/SourceFiles/observer_peer.h
@@ -67,6 +67,7 @@ struct PeerUpdate {
 		ChannelPromotedChanged    = (1 << 19),
 		ChannelLinkedChat         = (1 << 20),
 		ChannelLocation           = (1 << 21),
+		ChannelSlowmode           = (1 << 22),
 	};
 	using Flags = base::flags<Flag>;
 	friend inline constexpr auto is_flag_type(Flag) { return true; }