diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 46e0cab71..501172e79 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -859,6 +859,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 "lng_in_reply_to" = "In reply to";
 "lng_edited" = "edited";
 "lng_edited_date" = "Edited: {date}";
+"lng_admin_badge" = "admin";
 "lng_cancel_edit_post_sure" = "Cancel editing?";
 "lng_cancel_edit_post_yes" = "Yes";
 "lng_cancel_edit_post_no" = "No";
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 88dbc7332..f39ea7a36 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -639,7 +639,7 @@ void ApiWrap::lastParticipantsDone(
 
 	if (!peer->mgInfo) return;
 
-	parseChannelParticipants(result, [&](
+	parseChannelParticipants(peer, result, [&](
 			int availableCount,
 			const QVector<MTPChannelParticipant> &list) {
 		applyLastParticipantsList(
@@ -713,12 +713,12 @@ void ApiWrap::applyLastParticipantsList(
 				keyboardBotFound = true;
 			}
 		} else {
-			if (peer->mgInfo->lastParticipants.indexOf(u) < 0) {
+			if (!base::contains(peer->mgInfo->lastParticipants, u)) {
 				peer->mgInfo->lastParticipants.push_back(u);
 				if (adminRights.c_channelAdminRights().vflags.v) {
-					peer->mgInfo->lastAdmins.insert(u, MegagroupInfo::Admin { adminRights, adminCanEdit });
+					peer->mgInfo->lastAdmins.emplace(u, MegagroupInfo::Admin { adminRights, adminCanEdit });
 				} else if (restrictedRights.c_channelBannedRights().vflags.v != 0) {
-					peer->mgInfo->lastRestricted.insert(u, MegagroupInfo::Restricted { restrictedRights });
+					peer->mgInfo->lastRestricted.emplace(u, MegagroupInfo::Restricted { restrictedRights });
 				}
 				if (u->botInfo) {
 					peer->mgInfo->bots.insert(u);
@@ -1765,6 +1765,7 @@ void ApiWrap::readFeaturedSets() {
 }
 
 void ApiWrap::parseChannelParticipants(
+		not_null<ChannelData*> channel,
 		const MTPchannels_ChannelParticipants &result,
 		base::lambda<void(
 			int availableCount,
@@ -1773,6 +1774,9 @@ void ApiWrap::parseChannelParticipants(
 	TLHelp::VisitChannelParticipants(result, base::overload([&](
 			const MTPDchannels_channelParticipants &data) {
 		App::feedUsers(data.vusers);
+		if (channel->mgInfo) {
+			refreshChannelAdmins(channel, data.vparticipants.v);
+		}
 		if (callbackList) {
 			callbackList(data.vcount.v, data.vparticipants.v);
 		}
@@ -1783,7 +1787,31 @@ void ApiWrap::parseChannelParticipants(
 			LOG(("API Error: channels.channelParticipantsNotModified received!"));
 		}
 	}));
-};
+}
+
+void ApiWrap::refreshChannelAdmins(
+		not_null<ChannelData*> channel,
+		const QVector<MTPChannelParticipant> &participants) {
+	auto changes = base::flat_map<UserId, bool>();
+	auto &admins = channel->mgInfo->admins;
+	for (auto &participant : participants) {
+		auto userId = TLHelp::ReadChannelParticipantUserId(participant);
+		auto admin = (participant.type() == mtpc_channelParticipantAdmin)
+			|| (participant.type() == mtpc_channelParticipantCreator);
+		if (admin && !admins.contains(userId)) {
+			admins.insert(userId);
+			changes.emplace(userId, true);
+		} else if (!admin && admins.contains(userId)) {
+			admins.remove(userId);
+			changes.emplace(userId, false);
+		}
+	}
+	if (!changes.empty()) {
+		if (auto history = App::historyLoaded(channel)) {
+			history->applyGroupAdminChanges(changes);
+		}
+	}
+}
 
 void ApiWrap::parseRecentChannelParticipants(
 		not_null<ChannelData*> channel,
@@ -1792,7 +1820,7 @@ void ApiWrap::parseRecentChannelParticipants(
 			int availableCount,
 			const QVector<MTPChannelParticipant> &list)> callbackList,
 		base::lambda<void()> callbackNotModified) {
-	parseChannelParticipants(result, [&](
+	parseChannelParticipants(channel, result, [&](
 			int availableCount,
 			const QVector<MTPChannelParticipant> &list) {
 		auto applyLast = channel->isMegagroup()
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index efca429c3..f359bd385 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -144,6 +144,7 @@ public:
 	void readFeaturedSetDelayed(uint64 setId);
 
 	void parseChannelParticipants(
+		not_null<ChannelData*> channel,
 		const MTPchannels_ChannelParticipants &result,
 		base::lambda<void(
 			int availableCount,
@@ -215,6 +216,9 @@ private:
 	void cancelEditChatAdmins(not_null<ChatData*> chat);
 	void saveChatAdmins(not_null<ChatData*> chat);
 	void sendSaveChatAdminsRequests(not_null<ChatData*> chat);
+	void refreshChannelAdmins(
+		not_null<ChannelData*> channel,
+		const QVector<MTPChannelParticipant> &participants);
 
 	template <typename Callback>
 	void requestMessageAfterDate(
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 86b4495b1..94f59b4cc 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -798,7 +798,9 @@ namespace {
 				chat->version = d.vversion.v;
 				auto &v = d.vparticipants.v;
 				chat->count = v.size();
-				int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1);
+				int32 pversion = chat->participants.empty()
+					? 1
+					: (chat->participants.begin()->second + 1);
 				chat->invitedByMe.clear();
 				chat->admins.clear();
 				chat->removeFlags(MTPDchat::Flag::f_admin);
@@ -840,21 +842,22 @@ namespace {
 						break;
 					}
 				}
-				if (!chat->participants.isEmpty()) {
-					History *h = App::historyLoaded(chat->id);
+				if (!chat->participants.empty()) {
+					auto h = App::historyLoaded(chat->id);
 					bool found = !h || !h->lastKeyboardFrom;
-					int32 botStatus = -1;
+					auto botStatus = -1;
 					for (auto i = chat->participants.begin(), e = chat->participants.end(); i != e;) {
-						if (i.value() < pversion) {
+						auto [user, version] = *i;
+						if (version < pversion) {
 							i = chat->participants.erase(i);
 						} else {
-							if (i.key()->botInfo) {
-								botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
-								if (requestBotInfos && !i.key()->botInfo->inited) {
-									Auth().api().requestFullPeer(i.key());
+							if (user->botInfo) {
+								botStatus = 2;// (botStatus > 0/* || !user->botInfo->readsAllHistory*/) ? 2 : 1;
+								if (requestBotInfos && !user->botInfo->inited) {
+									Auth().api().requestFullPeer(user);
 								}
 							}
-							if (!found && i.key()->id == h->lastKeyboardFrom) {
+							if (!found && user->id == h->lastKeyboardFrom) {
 								found = true;
 							}
 							++i;
@@ -884,11 +887,11 @@ namespace {
 			chat->version = d.vversion.v;
 			UserData *user = App::userLoaded(d.vuser_id.v);
 			if (user) {
-				if (chat->participants.isEmpty() && chat->count) {
+				if (chat->participants.empty() && chat->count) {
 					chat->count++;
 					chat->botStatus = 0;
 				} else if (chat->participants.find(user) == chat->participants.end()) {
-					chat->participants[user] = (chat->participants.isEmpty() ? 1 : chat->participants.begin().value());
+					chat->participants[user] = (chat->participants.empty() ? 1 : chat->participants.begin()->second);
 					if (d.vinviter_id.v == Auth().userId()) {
 						chat->invitedByMe.insert(user);
 					} else {
@@ -921,7 +924,7 @@ namespace {
 			auto canEdit = chat->canEdit();
 			UserData *user = App::userLoaded(d.vuser_id.v);
 			if (user) {
-				if (chat->participants.isEmpty()) {
+				if (chat->participants.empty()) {
 					if (chat->count > 0) {
 						chat->count--;
 					}
@@ -943,9 +946,9 @@ namespace {
 					}
 					if (chat->botStatus > 0 && user->botInfo) {
 						int32 botStatus = -1;
-						for (auto j = chat->participants.cbegin(), e = chat->participants.cend(); j != e; ++j) {
-							if (j.key()->botInfo) {
-								if (true || botStatus > 0/* || !j.key()->botInfo->readsAllHistory*/) {
+						for (auto [participant, v] : chat->participants) {
+							if (participant->botInfo) {
+								if (true || botStatus > 0/* || !participant->botInfo->readsAllHistory*/) {
 									botStatus = 2;
 									break;
 								}
diff --git a/Telegram/SourceFiles/base/flat_map.h b/Telegram/SourceFiles/base/flat_map.h
index adc3e2482..bde096b92 100644
--- a/Telegram/SourceFiles/base/flat_map.h
+++ b/Telegram/SourceFiles/base/flat_map.h
@@ -47,29 +47,47 @@ template <
 	typename reference_impl>
 class flat_multi_map_iterator_base_impl;
 
-template <typename Key>
-class flat_multi_map_key_const_wrap {
-public:
-	constexpr flat_multi_map_key_const_wrap(const Key &value)
-	: _value(value) {
-	}
-	constexpr flat_multi_map_key_const_wrap(Key &&value)
-	: _value(std::move(value)) {
-	}
-	inline constexpr operator const Key&() const {
-		return _value;
+template <typename Key, typename Value>
+struct flat_multi_map_pair_type {
+	using first_type = const Key;
+	using second_type = Value;
+
+	constexpr flat_multi_map_pair_type()
+	: first()
+	, second() {
 	}
 
-private:
-	Key _value;
+	template <typename OtherKey, typename OtherValue>
+	constexpr flat_multi_map_pair_type(OtherKey &&key, OtherValue &&value)
+	: first(std::forward<OtherKey>(key))
+	, second(std::forward<OtherValue>(value)) {
+	}
 
+	flat_multi_map_pair_type(const flat_multi_map_pair_type&) = default;
+	flat_multi_map_pair_type(flat_multi_map_pair_type&&) = default;
+
+	flat_multi_map_pair_type &operator=(const flat_multi_map_pair_type&) = delete;
+	flat_multi_map_pair_type &operator=(flat_multi_map_pair_type &&other) {
+		const_cast<Key&>(first) = other.first;
+		second = std::move(other.second);
+		return *this;
+	}
+
+	void swap(flat_multi_map_pair_type &other) {
+		using std::swap;
+
+		if (this != &other) {
+			std::swap(
+				const_cast<Key&>(first),
+				const_cast<Key&>(other.first));
+			std::swap(second, other.second);
+		}
+	}
+
+	const Key first;
+	Value second;
 };
 
-template <typename Key, typename Type>
-using flat_multi_map_pair_type = std::pair<
-	flat_multi_map_key_const_wrap<Key>,
-	Type>;
-
 template <
 	typename Me,
 	typename Key,
@@ -230,7 +248,6 @@ public:
 	class const_reverse_iterator;
 
 private:
-	using key_const_wrap = flat_multi_map_key_const_wrap<Key>;
 	using pair_type = flat_multi_map_pair_type<Key, Type>;
 	using impl_t = std::deque<pair_type>;
 
@@ -477,9 +494,7 @@ private:
 			typename OtherType1,
 			typename OtherType2,
 			typename = std::enable_if_t<
-				!std::is_same_v<std::decay_t<OtherType1>, key_const_wrap> &&
 				!std::is_same_v<std::decay_t<OtherType1>, pair_type> &&
-				!std::is_same_v<std::decay_t<OtherType2>, key_const_wrap> &&
 				!std::is_same_v<std::decay_t<OtherType2>, pair_type>>>
 		inline constexpr auto operator()(
 				OtherType1 &&a,
@@ -488,42 +503,6 @@ private:
 				std::forward<OtherType1>(a),
 				std::forward<OtherType2>(b));
 		}
-		template <
-			typename OtherType1,
-			typename OtherType2>
-		inline constexpr auto operator()(
-				OtherType1 &&a,
-				OtherType2 &&b) const -> std::enable_if_t<
-		std::is_same_v<std::decay_t<OtherType1>, key_const_wrap> &&
-		std::is_same_v<std::decay_t<OtherType2>, key_const_wrap>, bool> {
-			return initial()(
-				static_cast<const Key&>(a),
-				static_cast<const Key&>(b));
-		}
-		template <
-			typename OtherType,
-			typename = std::enable_if_t<
-				!std::is_same_v<std::decay_t<OtherType>, key_const_wrap> &&
-				!std::is_same_v<std::decay_t<OtherType>, pair_type>>>
-		inline constexpr auto operator()(
-				const key_const_wrap &a,
-				OtherType &&b) const {
-			return initial()(
-				static_cast<const Key&>(a),
-				std::forward<OtherType>(b));
-		}
-		template <
-			typename OtherType,
-			typename = std::enable_if_t<
-				!std::is_same_v<std::decay_t<OtherType>, key_const_wrap> &&
-				!std::is_same_v<std::decay_t<OtherType>, pair_type>>>
-		inline constexpr auto operator()(
-				OtherType &&a,
-				const key_const_wrap &b) const {
-			return initial()(
-				std::forward<OtherType>(a),
-				static_cast<const Key&>(b));
-		}
 		template <
 			typename OtherType1,
 			typename OtherType2>
@@ -532,9 +511,7 @@ private:
 				OtherType2 &&b) const -> std::enable_if_t<
 		std::is_same_v<std::decay_t<OtherType1>, pair_type> &&
 		std::is_same_v<std::decay_t<OtherType2>, pair_type>, bool> {
-			return initial()(
-				static_cast<const Key&>(a.first),
-				static_cast<const Key&>(b.first));
+			return initial()(a.first, b.first);
 		}
 		template <
 			typename OtherType,
@@ -543,9 +520,7 @@ private:
 		inline constexpr auto operator()(
 				const pair_type &a,
 				OtherType &&b) const {
-			return operator()(
-				static_cast<const Key&>(a.first),
-				std::forward<OtherType>(b));
+			return operator()(a.first, std::forward<OtherType>(b));
 		}
 		template <
 			typename OtherType,
@@ -554,9 +529,7 @@ private:
 		inline constexpr auto operator()(
 				OtherType &&a,
 				const pair_type &b) const {
-			return operator()(
-				std::forward<OtherType>(a),
-				static_cast<const Key&>(b.first));
+			return operator()(std::forward<OtherType>(a), b.first);
 		}
 
 	};
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index adff1a767..aae0e4a04 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -38,8 +38,12 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
 		return {};
 	}
 	if (auto chat = peer->asChat()) {
-		auto participants = chat->participants.keys();
-		return { participants.cbegin(), participants.cend() };
+		auto participants = (
+			chat->participants
+		) | ranges::view::transform([](auto &&pair) -> not_null<UserData*> {
+			return pair.first;
+		});
+		return { participants.begin(), participants.end() };
 	} else if (auto channel = peer->asChannel()) {
 		if (channel->isMegagroup()) {
 			auto &participants = channel->mgInfo->lastParticipants;
@@ -427,7 +431,7 @@ bool AddParticipantsBoxController::isAlreadyIn(not_null<UserData*> user) const {
 		return chat->participants.contains(user);
 	} else if (auto channel = _peer->asChannel()) {
 		return _alreadyIn.contains(user)
-			|| (channel->isMegagroup() && channel->mgInfo->lastParticipants.contains(user));
+			|| (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user));
 	}
 	Unexpected("User in AddParticipantsBoxController::isAlreadyIn");
 }
@@ -632,12 +636,12 @@ void EditChatAdminsBoxController::rebuildRows() {
 	admins.reserve(allAdmins ? _chat->participants.size() : _chat->admins.size());
 	others.reserve(_chat->participants.size());
 
-	for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
-		if (i.key()->id == peerFromUser(_chat->creator)) continue;
-		if (_chat->admins.contains(i.key())) {
-			admins.push_back(i.key());
+	for (auto [user, version] : _chat->participants) {
+		if (user->id == peerFromUser(_chat->creator)) continue;
+		if (_chat->admins.contains(user)) {
+			admins.push_back(user);
 		} else {
-			others.push_back(i.key());
+			others.push_back(user);
 		}
 	}
 	if (!admins.empty()) {
diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp
index 975e1e139..e13b70f1e 100644
--- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp
+++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp
@@ -153,9 +153,9 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
 	} else if (_type == Type::Mentions) {
 		int maxListSize = _addInlineBots ? cRecentInlineBots().size() : 0;
 		if (_chat) {
-			maxListSize += (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size());
+			maxListSize += (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size());
 		} else if (_channel && _channel->isMegagroup()) {
-			if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
+			if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) {
 			} else {
 				maxListSize += _channel->mgInfo->lastParticipants.size();
 			}
@@ -192,19 +192,18 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
 		}
 		if (_chat) {
 			QMultiMap<int32, UserData*> ordered;
-			mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
+			mrows.reserve(mrows.size() + (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()));
 			if (_chat->noParticipantInfo()) {
 				Auth().api().requestFullPeer(_chat);
-			} else if (!_chat->participants.isEmpty()) {
-				for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
-					auto user = i.key();
+			} else if (!_chat->participants.empty()) {
+				for (const auto [user, v] : _chat->participants) {
 					if (user->isInaccessible()) continue;
 					if (!listAllSuggestions && filterNotPassedByName(user)) continue;
 					if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
 					ordered.insertMulti(App::onlineForSort(user, now), user);
 				}
 			}
-			for_const (auto user, _chat->lastAuthors) {
+			for (const auto user : _chat->lastAuthors) {
 				if (user->isInaccessible()) continue;
 				if (!listAllSuggestions && filterNotPassedByName(user)) continue;
 				if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
@@ -221,11 +220,11 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
 			}
 		} else if (_channel && _channel->isMegagroup()) {
 			QMultiMap<int32, UserData*> ordered;
-			if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
+			if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) {
 				Auth().api().requestLastParticipants(_channel);
 			} else {
 				mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
-				for_const (auto user, _channel->mgInfo->lastParticipants) {
+				for (const auto user : _channel->mgInfo->lastParticipants) {
 					if (user->isInaccessible()) continue;
 					if (!listAllSuggestions && filterNotPassedByName(user)) continue;
 					if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
@@ -251,9 +250,8 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
 		if (_chat) {
 			if (_chat->noParticipantInfo()) {
 				Auth().api().requestFullPeer(_chat);
-			} else if (!_chat->participants.isEmpty()) {
-				for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
-					auto user = i.key();
+			} else if (!_chat->participants.empty()) {
+				for (const auto [user, version] : _chat->participants) {
 					if (!user->botInfo) continue;
 					if (!user->botInfo->inited) {
 						Auth().api().requestFullPeer(user);
@@ -270,7 +268,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
 			cnt = _user->botInfo->commands.size();
 			bots.insert(_user, true);
 		} else if (_channel && _channel->isMegagroup()) {
-			if (_channel->mgInfo->bots.isEmpty()) {
+			if (_channel->mgInfo->bots.empty()) {
 				if (!_channel->mgInfo->botStatus) {
 					Auth().api().requestBots(_channel);
 				}
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 885d2d730..edbaa948b 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -806,7 +806,7 @@ void ChannelData::setInviteLink(const QString &newInviteLink) {
 
 void ChannelData::setMembersCount(int newMembersCount) {
 	if (_membersCount != newMembersCount) {
-		if (isMegagroup() && !mgInfo->lastParticipants.isEmpty()) {
+		if (isMegagroup() && !mgInfo->lastParticipants.empty()) {
 			mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
 			mgInfo->lastParticipantsCount = membersCount();
 		}
@@ -845,7 +845,8 @@ MTPChannelBannedRights ChannelData::KickedRestrictedRights() {
 void ChannelData::applyEditAdmin(not_null<UserData*> user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) {
 	auto flags = Notify::PeerUpdate::Flag::AdminsChanged | Notify::PeerUpdate::Flag::None;
 	if (mgInfo) {
-		if (!mgInfo->lastParticipants.contains(user)) { // If rights are empty - still add participant? TODO check
+		// If rights are empty - still add participant? TODO check
+		if (!base::contains(mgInfo->lastParticipants, user)) {
 			mgInfo->lastParticipants.push_front(user);
 			setMembersCount(membersCount() + 1);
 			if (user->botInfo && !mgInfo->bots.contains(user)) {
@@ -855,7 +856,8 @@ void ChannelData::applyEditAdmin(not_null<UserData*> user, const MTPChannelAdmin
 				}
 			}
 		}
-		if (mgInfo->lastRestricted.contains(user)) { // If rights are empty - still remove restrictions? TODO check
+		// If rights are empty - still remove restrictions? TODO check
+		if (mgInfo->lastRestricted.contains(user)) {
 			mgInfo->lastRestricted.remove(user);
 			if (restrictedCount() > 0) {
 				setRestrictedCount(restrictedCount() - 1);
@@ -866,10 +868,10 @@ void ChannelData::applyEditAdmin(not_null<UserData*> user, const MTPChannelAdmin
 			auto lastAdmin = MegagroupInfo::Admin { newRights };
 			lastAdmin.canEdit = true;
 			if (it == mgInfo->lastAdmins.cend()) {
-				mgInfo->lastAdmins.insert(user, lastAdmin);
+				mgInfo->lastAdmins.emplace(user, lastAdmin);
 				setAdminsCount(adminsCount() + 1);
 			} else {
-				it.value() = lastAdmin;
+				it->second = lastAdmin;
 			}
 		} else {
 			if (it != mgInfo->lastAdmins.cend()) {
@@ -913,10 +915,10 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
 		auto it = mgInfo->lastRestricted.find(user);
 		if (isRestricted) {
 			if (it == mgInfo->lastRestricted.cend()) {
-				mgInfo->lastRestricted.insert(user, MegagroupInfo::Restricted { newRights });
+				mgInfo->lastRestricted.emplace(user, MegagroupInfo::Restricted { newRights });
 				setRestrictedCount(restrictedCount() + 1);
 			} else {
-				it->rights = newRights;
+				it->second.rights = newRights;
 			}
 		} else {
 			if (it != mgInfo->lastRestricted.cend()) {
@@ -926,9 +928,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
 				}
 			}
 			if (isKicked) {
-				auto i = mgInfo->lastParticipants.indexOf(user);
-				if (i >= 0) {
-					mgInfo->lastParticipants.removeAt(i);
+				auto i = ranges::find(mgInfo->lastParticipants, user);
+				if (i != mgInfo->lastParticipants.end()) {
+					mgInfo->lastParticipants.erase(i);
 				}
 				if (membersCount() > 1) {
 					setMembersCount(membersCount() - 1);
@@ -939,7 +941,7 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
 				setKickedCount(kickedCount() + 1);
 				if (mgInfo->bots.contains(user)) {
 					mgInfo->bots.remove(user);
-					if (mgInfo->bots.isEmpty() && mgInfo->botStatus > 0) {
+					if (mgInfo->bots.empty() && mgInfo->botStatus > 0) {
 						mgInfo->botStatus = -1;
 					}
 				}
@@ -956,6 +958,13 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
 	Notify::peerUpdatedDelayed(this, flags);
 }
 
+bool ChannelData::isGroupAdmin(not_null<UserData*> user) const {
+	if (auto info = mgInfo.get()) {
+		return info->admins.contains(peerToUser(user->id));
+	}
+	return false;
+}
+
 void ChannelData::setRestrictionReason(const QString &text) {
 	if (_restrictionReason != text) {
 		_restrictionReason = text;
@@ -1087,9 +1096,9 @@ bool ChannelData::canDelete() const {
 bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const {
 	// Duplicated in ParticipantsBoxController::canEditAdmin :(
 	if (mgInfo) {
-		auto i = mgInfo->lastAdmins.constFind(user);
+		auto i = mgInfo->lastAdmins.find(user);
 		if (i != mgInfo->lastAdmins.cend()) {
-			return i->canEdit;
+			return i->second.canEdit;
 		}
 		return (user != mgInfo->creator);
 	}
@@ -1130,7 +1139,7 @@ void ChannelData::setAdminRights(const MTPChannelAdminRights &rights) {
 			if (!amCreator()) {
 				auto me = MegagroupInfo::Admin { rights };
 				me.canEdit = false;
-				mgInfo->lastAdmins.insert(App::self(), me);
+				mgInfo->lastAdmins.emplace(App::self(), me);
 			}
 			mgInfo->lastRestricted.remove(App::self());
 		} else {
@@ -1151,7 +1160,7 @@ void ChannelData::setRestrictedRights(const MTPChannelBannedRights &rights) {
 		if (hasRestrictions()) {
 			if (!amCreator()) {
 				auto me = MegagroupInfo::Restricted { rights };
-				mgInfo->lastRestricted.insert(App::self(), me);
+				mgInfo->lastRestricted.emplace(App::self(), me);
 			}
 			mgInfo->lastAdmins.remove(App::self());
 		} else {
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index cf931fc97..60ed0e64a 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -115,11 +115,6 @@ private:
 
 static const PhotoId UnknownPeerPhotoId = 0xFFFFFFFFFFFFFFFFULL;
 
-inline const QString &emptyUsername() {
-	static QString empty;
-	return empty;
-}
-
 class PeerData;
 
 class PeerClickHandler : public ClickHandler {
@@ -195,7 +190,7 @@ public:
 
 	const Text &dialogName() const;
 	const QString &shortName() const;
-	const QString &userName() const;
+	QString userName() const;
 
 	const PeerId id;
 	int32 bareId() const {
@@ -559,7 +554,7 @@ public:
 
 	void invalidateParticipants();
 	bool noParticipantInfo() const {
-		return (count > 0 || amIn()) && participants.isEmpty();
+		return (count > 0 || amIn()) && participants.empty();
 	}
 
 	MTPint inputChat;
@@ -623,11 +618,11 @@ public:
 	bool isMigrated() const {
 		return flags() & MTPDchat::Flag::f_migrated_to;
 	}
-	QMap<not_null<UserData*>, int> participants;
-	OrderedSet<not_null<UserData*>> invitedByMe;
-	OrderedSet<not_null<UserData*>> admins;
-	QList<not_null<UserData*>> lastAuthors;
-	OrderedSet<not_null<PeerData*>> markupSenders;
+	base::flat_map<not_null<UserData*>, int> participants;
+	base::flat_set<not_null<UserData*>> invitedByMe;
+	base::flat_set<not_null<UserData*>> admins;
+	std::deque<not_null<UserData*>> lastAuthors;
+	base::flat_set<not_null<PeerData*>> markupSenders;
 	int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
 //	ImagePtr photoFull;
 
@@ -744,11 +739,14 @@ struct MegagroupInfo {
 		}
 		MTPChannelBannedRights rights;
 	};
-	QList<not_null<UserData*>> lastParticipants;
-	QMap<not_null<UserData*>, Admin> lastAdmins;
-	QMap<not_null<UserData*>, Restricted> lastRestricted;
-	OrderedSet<not_null<PeerData*>> markupSenders;
-	OrderedSet<not_null<UserData*>> bots;
+	std::deque<not_null<UserData*>> lastParticipants;
+	base::flat_map<not_null<UserData*>, Admin> lastAdmins;
+	base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
+	base::flat_set<not_null<PeerData*>> markupSenders;
+	base::flat_set<not_null<UserData*>> bots;
+
+	// For admin badges, full admins list.
+	base::flat_set<UserId> admins;
 
 	UserData *creator = nullptr; // nullptr means unknown
 	int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
@@ -898,6 +896,8 @@ public:
 		const MTPChannelBannedRights &oldRights,
 		const MTPChannelBannedRights &newRights);
 
+	bool isGroupAdmin(not_null<UserData*> user) const;
+
 	int32 date = 0;
 	int version = 0;
 	std::unique_ptr<MegagroupInfo> mgInfo;
@@ -1176,12 +1176,12 @@ inline const Text &PeerData::dialogName() const {
 inline const QString &PeerData::shortName() const {
 	return isUser() ? asUser()->firstName : name;
 }
-inline const QString &PeerData::userName() const {
+inline QString PeerData::userName() const {
 	return isUser()
 		? asUser()->username
 		: isChannel()
 			? asChannel()->username
-			: emptyUsername();
+			: QString();
 }
 inline bool PeerData::isVerified() const {
 	return isUser()
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp
index cb8391a9a..364066711 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp
@@ -87,9 +87,8 @@ void ChatSearchFromController::rebuildRows() {
 	QMultiMap<int32, UserData*> ordered;
 	if (_chat->noParticipantInfo()) {
 		Auth().api().requestFullPeer(_chat);
-	} else if (!_chat->participants.isEmpty()) {
-		for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
-			auto user = i.key();
+	} else if (!_chat->participants.empty()) {
+		for (const auto [user, version] : _chat->participants) {
 			ordered.insertMulti(App::onlineForSort(user, now), user);
 		}
 	}
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index c9462f5de..8279a4c52 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -945,7 +945,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
 					auto &v = d.vusers.v;
 					for (auto i = 0, l = v.size(); i != l; ++i) {
 						if (auto user = App::userLoaded(peerFromUser(v[i]))) {
-							if (mgInfo->lastParticipants.indexOf(user) < 0) {
+							if (!base::contains(mgInfo->lastParticipants, user)) {
 								mgInfo->lastParticipants.push_front(user);
 								mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
 								Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
@@ -968,7 +968,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
 					auto mgInfo = megagroup->mgInfo.get();
 					Assert(mgInfo != nullptr);
 					if (auto user = result->from()->asUser()) {
-						if (mgInfo->lastParticipants.indexOf(user) < 0) {
+						if (!base::contains(mgInfo->lastParticipants, user)) {
 							mgInfo->lastParticipants.push_front(user);
 							Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
 							Auth().data().addNewMegagroupParticipant(megagroup, user);
@@ -998,9 +998,12 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
 					if (auto user = App::userLoaded(uid)) {
 						auto mgInfo = megagroup->mgInfo.get();
 						Assert(mgInfo != nullptr);
-						auto index = mgInfo->lastParticipants.indexOf(user);
-						if (index >= 0) {
-							mgInfo->lastParticipants.removeAt(index);
+						auto i = ranges::find(
+							mgInfo->lastParticipants,
+							user,
+							[](not_null<UserData*> user) { return user.get(); });
+						if (i != mgInfo->lastParticipants.end()) {
+							mgInfo->lastParticipants.erase(i);
 							Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
 						}
 						Auth().data().removeMegagroupParticipant(megagroup, user);
@@ -1018,7 +1021,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
 							Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged);
 						}
 						mgInfo->bots.remove(user);
-						if (mgInfo->bots.isEmpty() && mgInfo->botStatus > 0) {
+						if (mgInfo->bots.empty() && mgInfo->botStatus > 0) {
 							mgInfo->botStatus = -1;
 						}
 					}
@@ -1305,7 +1308,7 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
 	}
 	if (adding->from()->id) {
 		if (auto user = adding->from()->asUser()) {
-			auto getLastAuthors = [this]() -> QList<not_null<UserData*>>* {
+			auto getLastAuthors = [this]() -> std::deque<not_null<UserData*>>* {
 				if (auto chat = peer->asChat()) {
 					return &chat->lastAuthors;
 				} else if (auto channel = peer->asMegagroup()) {
@@ -1324,13 +1327,19 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
 				}
 			}
 			if (auto lastAuthors = getLastAuthors()) {
-				int prev = lastAuthors->indexOf(user);
-				if (prev > 0) {
-					lastAuthors->removeAt(prev);
-				} else if (prev < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
+				auto prev = ranges::find(
+					*lastAuthors,
+					user,
+					[](not_null<UserData*> user) { return user.get(); });
+				auto index = (prev != lastAuthors->end())
+					? (lastAuthors->end() - prev)
+					: -1;
+				if (index > 0) {
+					lastAuthors->erase(prev);
+				} else if (index < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
 					peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
 				}
-				if (prev) {
+				if (index) {
 					lastAuthors->push_front(user);
 				}
 				if (auto megagroup = peer->asMegagroup()) {
@@ -1342,7 +1351,7 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
 		if (adding->definesReplyKeyboard()) {
 			auto markupFlags = adding->replyKeyboardFlags();
 			if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || adding->mentionsMe()) {
-				auto getMarkupSenders = [this]() -> OrderedSet<not_null<PeerData*>>* {
+				auto getMarkupSenders = [this]() -> base::flat_set<not_null<PeerData*>>* {
 					if (auto chat = peer->asChat()) {
 						return &chat->markupSenders;
 					} else if (auto channel = peer->asMegagroup()) {
@@ -1360,7 +1369,7 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
 				} else {
 					bool botNotInChat = false;
 					if (peer->isChat()) {
-						botNotInChat = adding->from()->isUser() && (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from()->asUser());
+						botNotInChat = adding->from()->isUser() && (!peer->canWrite() || !peer->asChat()->participants.empty()) && !peer->asChat()->participants.contains(adding->from()->asUser());
 					} else if (peer->isMegagroup()) {
 						botNotInChat = adding->from()->isUser() && (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && !peer->asChannel()->mgInfo->bots.contains(adding->from()->asUser());
 					}
@@ -1577,8 +1586,8 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
 	} else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors
 		bool channel = isChannel();
 		int32 mask = 0;
-		QList<not_null<UserData*>> *lastAuthors = nullptr;
-		OrderedSet<not_null<PeerData*>> *markupSenders = nullptr;
+		std::deque<not_null<UserData*>> *lastAuthors = nullptr;
+		base::flat_set<not_null<PeerData*>> *markupSenders = nullptr;
 		if (peer->isChat()) {
 			lastAuthors = &peer->asChat()->lastAuthors;
 			markupSenders = &peer->asChat()->markupSenders;
@@ -1597,7 +1606,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
 			if (item->from()->id) {
 				if (lastAuthors) { // chats
 					if (auto user = item->from()->asUser()) {
-						if (!lastAuthors->contains(user)) {
+						if (!base::contains(*lastAuthors, user)) {
 							lastAuthors->push_back(user);
 							if (peer->isMegagroup()) {
 								peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
@@ -1620,7 +1629,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
 								if (!lastKeyboardInited) {
 									bool botNotInChat = false;
 									if (peer->isChat()) {
-										botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser());
+										botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.empty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser());
 									} else if (peer->isMegagroup()) {
 										botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser());
 									}
@@ -2408,6 +2417,15 @@ void History::clearUpTill(MsgId availableMinId) {
 	}
 }
 
+void History::applyGroupAdminChanges(
+		const base::flat_map<UserId, bool> &changes) {
+	for (auto block : blocks) {
+		for (auto item : block->items) {
+			item->applyGroupAdminChanges(changes);
+		}
+	}
+}
+
 void History::clearBlocks(bool leaveItems) {
 	Blocks lst;
 	std::swap(lst, blocks);
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 9fd8ea805..a31f80804 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -223,6 +223,8 @@ public:
 	void clear(bool leaveItems = false);
 	void clearUpTill(MsgId availableMinId);
 
+	void applyGroupAdminChanges(const base::flat_map<UserId, bool> &changes);
+
 	virtual ~History();
 
 	HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
index 21c771e19..a17428507 100644
--- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
@@ -357,7 +357,7 @@ void InnerWidget::requestAdmins() {
 		}, [](auto &&) {
 			return false;
 		});
-		Auth().api().parseChannelParticipants(result, [&](
+		Auth().api().parseChannelParticipants(_channel, result, [&](
 				int availableCount,
 				const QVector<MTPChannelParticipant> &list) {
 			auto filtered = (
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 98f67329d..0f34379e9 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -1176,7 +1176,7 @@ void HistoryItem::audioTrackUpdated() {
 
 void HistoryItem::recountDisplayDate() {
 	Expects(!isLogEntry());
-	setDisplayDate(([this]() {
+	setDisplayDate([&] {
 		if (isEmpty()) {
 			return false;
 		}
@@ -1185,7 +1185,7 @@ void HistoryItem::recountDisplayDate() {
 			return previous->isEmpty() || (previous->date.date() != date.date());
 		}
 		return true;
-	})());
+	}());
 }
 
 void HistoryItem::setDisplayDate(bool displayDate) {
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 28e0d6677..5398313eb 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -520,6 +520,9 @@ public:
 	virtual bool notificationReady() const {
 		return true;
 	}
+	virtual void applyGroupAdminChanges(
+		const base::flat_map<UserId, bool> &changes) {
+	}
 
 	UserData *viaBot() const {
 		if (auto via = Get<HistoryMessageVia>()) {
@@ -822,9 +825,6 @@ public:
 		return 0;
 	}
 
-	bool hasFromName() const {
-		return (!out() || isPost()) && !history()->peer->isUser();
-	}
 	PeerData *author() const {
 		return isPost() ? history()->peer : from();
 	}
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index abd3ca227..39d60facb 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -50,6 +50,10 @@ inline void initTextOptions() {
 	_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
 }
 
+QString AdminBadgeText() {
+	return lang(lng_admin_badge);
+}
+
 style::color FromNameFg(not_null<PeerData*> peer, bool selected) {
 	if (selected) {
 		const style::color colors[] = {
@@ -805,6 +809,35 @@ void HistoryMessage::updateMediaInBubbleState() {
 	_media->setInBubbleState(computeState());
 }
 
+void HistoryMessage::updateAdminBadgeState() {
+	auto hasAdminBadge = [&] {
+		if (auto channel = history()->peer->asChannel()) {
+			if (auto user = author()->asUser()) {
+				return channel->isGroupAdmin(user);
+			}
+		}
+		return false;
+	}();
+	if (hasAdminBadge) {
+		_flags |= MTPDmessage_ClientFlag::f_has_admin_badge;
+	} else {
+		_flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge;
+	}
+}
+
+void HistoryMessage::applyGroupAdminChanges(
+		const base::flat_map<UserId, bool> &changes) {
+	auto i = changes.find(peerToUser(author()->id));
+	if (i != changes.end()) {
+		if (i->second) {
+			_flags |= MTPDmessage_ClientFlag::f_has_admin_badge;
+		} else {
+			_flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge;
+		}
+		setPendingInitDimensions();
+	}
+}
+
 bool HistoryMessage::displayEditedBadge(bool hasViaBotOrInlineMarkup) const {
 	if (hasViaBotOrInlineMarkup) {
 		return false;
@@ -1062,6 +1095,9 @@ void HistoryMessage::initDimensions() {
 		if (reply) {
 			reply->updateName();
 		}
+		if (displayFromName()) {
+			updateAdminBadgeState();
+		}
 
 		auto mediaDisplayed = false;
 		if (_media) {
@@ -1111,6 +1147,11 @@ void HistoryMessage::initDimensions() {
 				if (via && !forwarded) {
 					namew += st::msgServiceFont->spacew + via->_maxWidth;
 				}
+				if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
+					auto badgeWidth = st::msgServiceFont->width(
+						AdminBadgeText());
+					namew += st::msgPadding.right() + badgeWidth;
+				}
 				accumulate_max(_maxw, namew);
 			} else if (via && !forwarded) {
 				accumulate_max(_maxw, st::msgPadding.left() + via->_maxWidth + st::msgPadding.right());
@@ -1188,10 +1229,19 @@ QRect HistoryMessage::countGeometry() const {
 }
 
 void HistoryMessage::fromNameUpdated(int32 width) const {
+	if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
+		auto badgeWidth = st::msgServiceFont->width(
+			AdminBadgeText());
+		width -= st::msgPadding.right() + badgeWidth;
+	}
 	_authorNameVersion = author()->nameVersion;
 	if (!Has<HistoryMessageForwarded>()) {
 		if (auto via = Get<HistoryMessageVia>()) {
-			via->resize(width - st::msgPadding.left() - st::msgPadding.right() - author()->nameText.maxWidth() - st::msgServiceFont->spacew);
+			via->resize(width
+				- st::msgPadding.left()
+				- st::msgPadding.right()
+				- author()->nameText.maxWidth()
+				- st::msgServiceFont->spacew);
 		}
 	}
 }
@@ -1774,20 +1824,46 @@ void HistoryMessage::drawFastShare(Painter &p, int left, int top, int outerWidth
 
 void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) const {
 	if (displayFromName()) {
+		auto badgeWidth = [&] {
+			if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
+				return st::msgServiceFont->width(AdminBadgeText());
+			}
+			return 0;
+		}();
+		auto availableLeft = trect.left();
+		auto availableWidth = trect.width();
+		if (badgeWidth) {
+			availableWidth -= st::msgPadding.right() + badgeWidth;
+		}
+
 		p.setFont(st::msgNameFont);
 		if (isPost()) {
 			p.setPen(selected ? st::msgInServiceFgSelected : st::msgInServiceFg);
 		} else {
 			p.setPen(FromNameFg(author(), selected));
 		}
-		author()->nameText.drawElided(p, trect.left(), trect.top(), trect.width());
+		author()->nameText.drawElided(p, availableLeft, trect.top(), availableWidth);
+		auto skipWidth = author()->nameText.maxWidth() + st::msgServiceFont->spacew;
+		availableLeft += skipWidth;
+		availableWidth -= skipWidth;
 
 		auto forwarded = Get<HistoryMessageForwarded>();
 		auto via = Get<HistoryMessageVia>();
-		if (via && !forwarded && trect.width() > author()->nameText.maxWidth() + st::msgServiceFont->spacew) {
-			bool outbg = out() && !isPost();
+		if (via && !forwarded && availableWidth > 0) {
+			auto outbg = out() && !isPost();
 			p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
-			p.drawText(trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew, trect.top() + st::msgServiceFont->ascent, via->_text);
+			p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->_text);
+			auto skipWidth = via->_width + st::msgServiceFont->spacew;
+			availableLeft += skipWidth;
+			availableWidth -= skipWidth;
+		}
+		if (badgeWidth) {
+			p.setPen(selected ? st::msgInDateFgSelected : st::msgInDateFg);
+			p.setFont(st::msgFont);
+			p.drawText(
+				trect.left() + trect.width() - badgeWidth,
+				trect.top() + st::msgFont->ascent,
+				AdminBadgeText());
 		}
 		trect.setY(trect.y() + st::msgNameFont->height);
 	}
diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h
index 85dbb2503..56befe8c7 100644
--- a/Telegram/SourceFiles/history/history_message.h
+++ b/Telegram/SourceFiles/history/history_message.h
@@ -62,6 +62,9 @@ public:
 	bool hasBubble() const override {
 		return drawBubble();
 	}
+	bool hasFromName() const {
+		return (!out() || isPost()) && !history()->peer->isUser();
+	}
 	bool displayFromName() const {
 		if (!hasFromName()) return false;
 		if (isAttachedToPrevious()) return false;
@@ -71,6 +74,9 @@ public:
 	bool uploading() const;
 	bool displayFastShare() const override;
 
+	void applyGroupAdminChanges(
+		const base::flat_map<UserId, bool> &changes) override;
+
 	void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override;
 	void drawFastShare(Painter &p, int left, int top, int outerWidth) const override;
 	void setViewsCount(int32 count) override;
@@ -229,5 +235,6 @@ private:
 	};
 
 	void updateMediaInBubbleState();
+	void updateAdminBadgeState();
 
 };
diff --git a/Telegram/SourceFiles/history/history_top_bar_widget.cpp b/Telegram/SourceFiles/history/history_top_bar_widget.cpp
index d9bd7dcf1..6046103aa 100644
--- a/Telegram/SourceFiles/history/history_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/history_top_bar_widget.cpp
@@ -639,7 +639,7 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
 	} else if (auto chat = _historyPeer->asChat()) {
 		if (!chat->amIn()) {
 			text = lang(lng_chat_status_unaccessible);
-		} else if (chat->participants.isEmpty()) {
+		} else if (chat->participants.empty()) {
 			if (!_titlePeerText.isEmpty()) {
 				text = _titlePeerText;
 			} else if (chat->count <= 0) {
@@ -650,10 +650,10 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
 		} else {
 			auto online = 0;
 			auto onlyMe = true;
-			for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
-				if (i.key()->onlineTill > t) {
+			for (auto [user, v] : chat->participants) {
+				if (user->onlineTill > t) {
 					++online;
-					if (onlyMe && i.key() != App::self()) onlyMe = false;
+					if (onlyMe && user != App::self()) onlyMe = false;
 				}
 			}
 			if (online > 0 && !onlyMe) {
@@ -668,7 +668,7 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
 		}
 	} else if (auto channel = _historyPeer->asChannel()) {
 		if (channel->isMegagroup() && channel->membersCount() > 0 && channel->membersCount() <= Global::ChatSizeMax()) {
-			if (channel->mgInfo->lastParticipants.isEmpty() || channel->lastParticipantsCountOutdated()) {
+			if (channel->mgInfo->lastParticipants.empty() || channel->lastParticipantsCountOutdated()) {
 				Auth().api().requestLastParticipants(channel);
 			}
 			auto online = 0;
@@ -713,10 +713,10 @@ void HistoryTopBarWidget::updateOnlineDisplayTimer() {
 	if (auto user = _historyPeer->asUser()) {
 		minIn = App::onlineWillChangeIn(user, t);
 	} else if (auto chat = _historyPeer->asChat()) {
-		if (chat->participants.isEmpty()) return;
+		if (chat->participants.empty()) return;
 
-		for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
-			int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key(), t);
+		for (auto [user, v] : chat->participants) {
+			auto onlineWillChangeIn = App::onlineWillChangeIn(user, t);
 			if (onlineWillChangeIn < minIn) {
 				minIn = onlineWillChangeIn;
 			}
diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
index 608f16cf5..8d7234b24 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
@@ -365,9 +365,9 @@ void Cover::refreshStatusText() {
 			if (!chat->amIn()) {
 				return lang(lng_chat_status_unaccessible);
 			}
-			auto fullCount = qMax(
+			auto fullCount = std::max(
 				chat->count,
-				chat->participants.size());
+				int(chat->participants.size()));
 			return ChatStatusText(fullCount, _onlineCount, true);
 		} else if (auto channel = _peer->asChannel()) {
 			auto fullCount = qMax(channel->membersCount(), 1);
diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp
index 31218eccb..0a8f76140 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp
@@ -188,10 +188,8 @@ void ChatMembersController::rebuildRows() {
 			--count;
 		}
 	}
-	for (auto i = participants.cbegin(), e = participants.cend();
-		i != e;
-		++i) {
-		if (auto row = createRow(i.key())) {
+	for (const auto [user, v] : participants) {
+		if (auto row = createRow(user)) {
 			delegate()->peerListAppendRow(std::move(row));
 		}
 	}
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
index 4fe41fe86..cbe4daa23 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
@@ -186,7 +186,7 @@ rpl::producer<int> MembersCountValue(
 			Notify::PeerUpdate::Flag::MembersChanged)
 			| rpl::map([chat] {
 				return chat->amIn()
-					? qMax(chat->count, chat->participants.size())
+					? std::max(chat->count, int(chat->participants.size()))
 					: 0;
 			});
 	} else if (auto channel = peer->asChannel()) {
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 1b33bef28..d540ae1ca 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -5045,7 +5045,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 			// 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.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) {
+				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) {
 					Auth().api().requestLastParticipants(channel);
 				}
 			}
diff --git a/Telegram/SourceFiles/mtproto/type_utils.h b/Telegram/SourceFiles/mtproto/type_utils.h
index 9a3605226..6e704bd52 100644
--- a/Telegram/SourceFiles/mtproto/type_utils.h
+++ b/Telegram/SourceFiles/mtproto/type_utils.h
@@ -78,8 +78,11 @@ enum class MTPDmessage_ClientFlag : uint32 {
 	// message is generated on the client side and should be unread
 	f_clientside_unread = (1U << 21),
 
+	// message has an admin badge in supergroup
+	f_has_admin_badge = (1U << 20),
+
 	// update this when adding new client side flags
-	MIN_FIELD = (1U << 21),
+	MIN_FIELD = (1U << 20),
 };
 DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
 
diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp
index 45c743bb8..985b4f857 100644
--- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp
+++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp
@@ -78,7 +78,7 @@ void GroupMembersWidget::editAdmin(not_null<UserData*> user) {
 	}
 	auto currentRightsIt = megagroup->mgInfo->lastAdmins.find(user);
 	auto hasAdminRights = (currentRightsIt != megagroup->mgInfo->lastAdmins.cend());
-	auto currentRights = hasAdminRights ? currentRightsIt->rights : MTP_channelAdminRights(MTP_flags(0));
+	auto currentRights = hasAdminRights ? currentRightsIt->second.rights : MTP_channelAdminRights(MTP_flags(0));
 	auto weak = QPointer<GroupMembersWidget>(this);
 	auto box = Box<EditAdminBox>(megagroup, user, currentRights);
 	box->setSaveCallback([weak, megagroup, user](const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) {
@@ -96,8 +96,10 @@ void GroupMembersWidget::restrictUser(not_null<UserData*> user) {
 	if (!megagroup) {
 		return; // not supported
 	}
-	auto defaultRestricted = MegagroupInfo::Restricted { MTP_channelBannedRights(MTP_flags(0), MTP_int(0)) };
-	auto currentRights = megagroup->mgInfo->lastRestricted.value(user, defaultRestricted).rights;
+	auto currentRightsIt = megagroup->mgInfo->lastRestricted.find(user);
+	auto currentRights = (currentRightsIt != megagroup->mgInfo->lastRestricted.end())
+		? currentRightsIt->second.rights
+		: MTP_channelBannedRights(MTP_flags(0), MTP_int(0));
 	auto hasAdminRights = megagroup->mgInfo->lastAdmins.find(user) != megagroup->mgInfo->lastAdmins.cend();
 	auto box = Box<EditRestrictedBox>(megagroup, user, hasAdminRights, currentRights);
 	box->setSaveCallback([megagroup, user](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) {
@@ -114,13 +116,15 @@ void GroupMembersWidget::removePeer(PeerData *selectedPeer) {
 	auto user = selectedPeer->asUser();
 	Assert(user != nullptr);
 	auto text = lng_profile_sure_kick(lt_user, user->firstName);
-	auto currentRestrictedRights = MTP_channelBannedRights(MTP_flags(0), MTP_int(0));
-	if (auto channel = peer()->asMegagroup()) {
-		auto it = channel->mgInfo->lastRestricted.find(user);
-		if (it != channel->mgInfo->lastRestricted.cend()) {
-			currentRestrictedRights = it->rights;
+	auto currentRestrictedRights = [&]() -> MTPChannelBannedRights {
+		if (auto channel = peer()->asMegagroup()) {
+			auto it = channel->mgInfo->lastRestricted.find(user);
+			if (it != channel->mgInfo->lastRestricted.cend()) {
+				return it->second.rights;
+			}
 		}
-	}
+		return MTP_channelBannedRights(MTP_flags(0), MTP_int(0));
+	}();
 	Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [user, currentRestrictedRights, peer = peer()] {
 		Ui::hideLayer();
 		if (auto chat = peer->asChat()) {
@@ -303,7 +307,7 @@ void GroupMembersWidget::refreshMembers() {
 		refreshLimitReached();
 	} else if (auto megagroup = peer()->asMegagroup()) {
 		auto &megagroupInfo = megagroup->mgInfo;
-		if (megagroupInfo->lastParticipants.isEmpty() || megagroup->lastParticipantsCountOutdated()) {
+		if (megagroupInfo->lastParticipants.empty() || megagroup->lastParticipantsCountOutdated()) {
 			Auth().api().requestLastParticipants(megagroup);
 		}
 		fillMegagroupMembers(megagroup);
@@ -335,7 +339,7 @@ void GroupMembersWidget::refreshLimitReached() {
 }
 
 void GroupMembersWidget::checkSelfAdmin(ChatData *chat) {
-	if (chat->participants.isEmpty()) return;
+	if (chat->participants.empty()) return;
 
 	auto self = App::self();
 	if (chat->amAdmin() && !chat->admins.contains(self)) {
@@ -390,7 +394,7 @@ GroupMembersWidget::Member *GroupMembersWidget::addUser(ChatData *chat, UserData
 }
 
 void GroupMembersWidget::fillChatMembers(ChatData *chat) {
-	if (chat->participants.isEmpty()) return;
+	if (chat->participants.empty()) return;
 
 	clearItems();
 	if (!chat->amIn()) return;
@@ -399,8 +403,7 @@ void GroupMembersWidget::fillChatMembers(ChatData *chat) {
 
 	reserveItemsForSize(chat->participants.size());
 	addUser(chat, App::self())->onlineForSort = INT_MAX; // Put me on the first place.
-	for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
-		auto user = i.key();
+	for (auto [user, v] : chat->participants) {
 		if (!user->isSelf()) {
 			addUser(chat, user);
 		}
@@ -434,7 +437,7 @@ GroupMembersWidget::Member *GroupMembersWidget::addUser(ChannelData *megagroup,
 
 void GroupMembersWidget::fillMegagroupMembers(ChannelData *megagroup) {
 	Assert(megagroup->mgInfo != nullptr);
-	if (megagroup->mgInfo->lastParticipants.isEmpty()) return;
+	if (megagroup->mgInfo->lastParticipants.empty()) return;
 
 	if (!megagroup->canViewMembers()) {
 		clearItems();
@@ -485,10 +488,10 @@ void GroupMembersWidget::setItemFlags(Item *item, ChannelData *megagroup) {
 	using AdminState = Item::AdminState;
 	auto amCreator = item->peer->isSelf() && megagroup->amCreator();
 	auto amAdmin = item->peer->isSelf() && megagroup->hasAdminRights();
-	auto adminIt = megagroup->mgInfo->lastAdmins.constFind(getMember(item)->user());
+	auto adminIt = megagroup->mgInfo->lastAdmins.find(getMember(item)->user());
 	auto isAdmin = (adminIt != megagroup->mgInfo->lastAdmins.cend());
 	auto isCreator = megagroup->mgInfo->creator == item->peer;
-	auto adminCanEdit = isAdmin && adminIt->canEdit;
+	auto adminCanEdit = isAdmin && adminIt->second.canEdit;
 	auto adminState = (amCreator || isCreator) ? AdminState::Creator : (amAdmin || isAdmin) ? AdminState::Admin : AdminState::None;
 	if (item->adminState != adminState) {
 		item->adminState = adminState;
diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp
index 9ae316c47..c87964d8b 100644
--- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp
+++ b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp
@@ -475,6 +475,7 @@ void ParticipantsBoxController::loadMoreRows() {
 					callback);
 			} else {
 				Auth().api().parseChannelParticipants(
+					_channel,
 					result,
 					callback);
 			}
@@ -538,7 +539,7 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
 	//	_channel->updateFull();
 	//	return false;
 	//}
-	if (info->lastParticipants.isEmpty()) {
+	if (info->lastParticipants.empty()) {
 		return false;
 	}
 
@@ -546,21 +547,21 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
 		_additional.creator = info->creator;
 	}
 	for_const (auto user, info->lastParticipants) {
-		auto admin = info->lastAdmins.constFind(user);
+		auto admin = info->lastAdmins.find(user);
 		if (admin != info->lastAdmins.cend()) {
 			_additional.restrictedRights.erase(user);
-			if (admin->canEdit) {
+			if (admin->second.canEdit) {
 				_additional.adminCanEdit.emplace(user);
 			} else {
 				_additional.adminCanEdit.erase(user);
 			}
-			_additional.adminRights.emplace(user, admin->rights);
+			_additional.adminRights.emplace(user, admin->second.rights);
 		} else {
 			_additional.adminCanEdit.erase(user);
 			_additional.adminRights.erase(user);
-			auto restricted = info->lastRestricted.constFind(user);
+			auto restricted = info->lastRestricted.find(user);
 			if (restricted != info->lastRestricted.cend()) {
-				_additional.restrictedRights.emplace(user, restricted->rights);
+				_additional.restrictedRights.emplace(user, restricted->second.rights);
 			} else {
 				_additional.restrictedRights.erase(user);
 			}
@@ -1083,7 +1084,7 @@ void ParticipantsBoxSearchController::searchDone(
 		int requestedCount) {
 	auto query = _query;
 	if (requestId) {
-		Auth().api().parseChannelParticipants(result, [&](auto&&...) {
+		Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) {
 			auto it = _queries.find(requestId);
 			if (it != _queries.cend()) {
 				query = it->second.text;
@@ -1184,7 +1185,7 @@ void AddParticipantBoxController::loadMoreRows() {
 	)).done([this](const MTPchannels_ChannelParticipants &result) {
 		_loadRequestId = 0;
 
-		Auth().api().parseChannelParticipants(result, [&](
+		Auth().api().parseChannelParticipants(_channel, result, [&](
 				int availableCount,
 				const QVector<MTPChannelParticipant> &list) {
 			for (auto &participant : list) {
@@ -1720,7 +1721,7 @@ void AddParticipantBoxSearchController::requestParticipants() {
 void AddParticipantBoxSearchController::searchParticipantsDone(mtpRequestId requestId, const MTPchannels_ChannelParticipants &result, int requestedCount) {
 	auto query = _query;
 	if (requestId) {
-		Auth().api().parseChannelParticipants(result, [&](auto&&...) {
+		Auth().api().parseChannelParticipants(_channel, result, [&](auto&&...) {
 			auto it = _participantsQueries.find(requestId);
 			if (it != _participantsQueries.cend()) {
 				query = it->second.text;
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h
index 34bdc4e49..bc8b4bc8d 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.h
+++ b/Telegram/SourceFiles/window/notifications_manager_default.h
@@ -98,7 +98,7 @@ private:
 		QueuedNotification(HistoryItem *item, int forwardedCount)
 		: history(item->history())
 		, peer(history->peer)
-		, author((item->hasFromName() && !item->isPost()) ? item->author() : nullptr)
+		, author((!peer->isUser() && !item->isPost()) ? item->author() : nullptr)
 		, item((forwardedCount > 1) ? nullptr : item)
 		, forwardedCount(forwardedCount) {
 		}
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 8e84f2fb3..0c7f8bbf8 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -510,7 +510,7 @@ void PeerMenuAddChannelMembers(not_null<ChannelData*> channel) {
 		return;
 	}
 	auto callback = [channel](const MTPchannels_ChannelParticipants &result) {
-		Auth().api().parseChannelParticipants(result, [&](
+		Auth().api().parseChannelParticipants(channel, result, [&](
 				int availableCount,
 				const QVector<MTPChannelParticipant> &list) {
 			auto already = (