From b9de12fedb37ac5b44a5c1870888f85c2470e9c2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 14 Aug 2024 15:17:59 +0200
Subject: [PATCH] Support 18+/restrictions for messages.

---
 Telegram/Resources/langs/lang.strings         |   7 +
 .../SourceFiles/api/api_sensitive_content.cpp |  66 +++++--
 .../SourceFiles/api/api_sensitive_content.h   |   9 +-
 .../boxes/peers/edit_peer_info_box.cpp        |   2 +-
 .../chat_helpers/ttl_media_layer_widget.cpp   |   2 +-
 Telegram/SourceFiles/data/data_channel.cpp    |   7 +-
 Telegram/SourceFiles/data/data_channel.h      |   6 +-
 .../data/data_download_manager.cpp            |   2 +-
 Telegram/SourceFiles/data/data_peer.cpp       |  58 +++++-
 Telegram/SourceFiles/data/data_peer.h         |  13 +-
 Telegram/SourceFiles/data/data_photo.cpp      |   3 +-
 Telegram/SourceFiles/data/data_session.cpp    |  48 ++++-
 Telegram/SourceFiles/data/data_session.h      |  18 +-
 Telegram/SourceFiles/data/data_types.h        |   2 +-
 Telegram/SourceFiles/data/data_user.cpp       |  14 +-
 Telegram/SourceFiles/data/data_user.h         |   6 +-
 Telegram/SourceFiles/history/history_item.cpp |  28 ++-
 Telegram/SourceFiles/history/history_item.h   |   5 +-
 .../history/view/history_view_element.cpp     |  11 +-
 .../history/view/media/history_view_gif.cpp   |  66 +++++--
 .../history/view/media/history_view_gif.h     |  10 +
 .../history/view/media/history_view_media.cpp | 157 +++++++++++++++-
 .../history/view/media/history_view_media.h   |  24 ++-
 .../view/media/history_view_media_common.cpp  |  64 +++++++
 .../view/media/history_view_media_common.h    |   6 +-
 .../view/media/history_view_media_grouped.cpp |  25 ++-
 .../view/media/history_view_media_grouped.h   |   4 +-
 .../view/media/history_view_media_spoiler.h   |  10 +
 .../history/view/media/history_view_photo.cpp | 172 ++++--------------
 .../history/view/media/history_view_photo.h   |  14 +-
 Telegram/SourceFiles/main/main_app_config.cpp |  35 +++-
 Telegram/SourceFiles/main/main_app_config.h   |  16 +-
 .../SourceFiles/settings/settings_chat.cpp    |   8 +-
 .../settings/settings_privacy_security.cpp    |  87 +++++----
 .../settings/settings_privacy_security.h      |   5 +
 35 files changed, 711 insertions(+), 299 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 2aeb46f50..013244b04 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -3485,6 +3485,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_paid_react_show_in_top" = "Show me in Top Senders";
 "lng_paid_react_anonymous" = "Anonymous";
 
+"lng_sensitive_tag" = "18+";
+"lng_sensitive_title" = "18+";
+"lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
+"lng_sensitive_always" = "Always show 18+ media";
+"lng_sensitive_view" = "View Anyway";
+"lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
+
 "lng_translate_show_original" = "Show Original";
 "lng_translate_bar_to" = "Translate to {name}";
 "lng_translate_bar_to_other" = "Translate to {name}";
diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp
index 97388d638..31f83f5eb 100644
--- a/Telegram/SourceFiles/api/api_sensitive_content.cpp
+++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace Api {
 namespace {
 
-constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000);
+constexpr auto kRefreshAppConfigTimeout = crl::time(1);
 
 } // namespace
 
@@ -24,19 +24,40 @@ SensitiveContent::SensitiveContent(not_null<ApiWrap*> api)
 , _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
 }
 
-void SensitiveContent::reload() {
-	if (_requestId) {
+void SensitiveContent::preload() {
+	if (!_loaded) {
+		reload();
+	}
+}
+
+void SensitiveContent::reload(bool force) {
+	if (_loadRequestId) {
+		if (force) {
+			_loadPending = true;
+		}
 		return;
 	}
-	_requestId = _api.request(MTPaccount_GetContentSettings(
+	_loaded = true;
+	_loadRequestId = _api.request(MTPaccount_GetContentSettings(
 	)).done([=](const MTPaccount_ContentSettings &result) {
-		_requestId = 0;
-		result.match([&](const MTPDaccount_contentSettings &data) {
-			_enabled = data.is_sensitive_enabled();
-			_canChange = data.is_sensitive_can_change();
-		});
+		_loadRequestId = 0;
+		const auto &data = result.data();
+		const auto enabled = data.is_sensitive_enabled();
+		const auto canChange = data.is_sensitive_can_change();
+		const auto changed = (_enabled.current() != enabled)
+			|| (_canChange.current() != canChange);
+		if (changed) {
+			_enabled = enabled;
+			_canChange = canChange;
+		}
+		if (base::take(_appConfigReloadForce) || changed) {
+			_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
+		}
+		if (base::take(_loadPending)) {
+			reload();
+		}
 	}).fail([=] {
-		_requestId = 0;
+		_loadRequestId = 0;
 	}).send();
 }
 
@@ -57,17 +78,24 @@ void SensitiveContent::update(bool enabled) {
 		return;
 	}
 	using Flag = MTPaccount_SetContentSettings::Flag;
-	_api.request(_requestId).cancel();
-	_requestId = _api.request(MTPaccount_SetContentSettings(
+	_api.request(_saveRequestId).cancel();
+	if (const auto load = base::take(_loadRequestId)) {
+		_api.request(load).cancel();
+		_loadPending = true;
+	}
+	const auto finish = [=] {
+		_saveRequestId = 0;
+		if (base::take(_loadPending)) {
+			_appConfigReloadForce = true;
+			reload(true);
+		} else {
+			_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
+		}
+	};
+	_saveRequestId = _api.request(MTPaccount_SetContentSettings(
 		MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0))
-	)).done([=] {
-		_requestId = 0;
-	}).fail([=] {
-		_requestId = 0;
-	}).send();
+	)).done(finish).fail(finish).send();
 	_enabled = enabled;
-
-	_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
 }
 
 } // namespace Api
diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h
index 0a61f9da7..576bf275f 100644
--- a/Telegram/SourceFiles/api/api_sensitive_content.h
+++ b/Telegram/SourceFiles/api/api_sensitive_content.h
@@ -22,7 +22,8 @@ class SensitiveContent final {
 public:
 	explicit SensitiveContent(not_null<ApiWrap*> api);
 
-	void reload();
+	void preload();
+	void reload(bool force = false);
 	void update(bool enabled);
 
 	[[nodiscard]] bool enabledCurrent() const;
@@ -32,10 +33,14 @@ public:
 private:
 	const not_null<Main::Session*> _session;
 	MTP::Sender _api;
-	mtpRequestId _requestId = 0;
+	mtpRequestId _loadRequestId = 0;
+	mtpRequestId _saveRequestId = 0;
 	rpl::variable<bool> _enabled = false;
 	rpl::variable<bool> _canChange = false;
 	base::Timer _appConfigReloadTimer;
+	bool _appConfigReloadForce = false;
+	bool _loadPending = false;
+	bool _loaded = false;
 
 };
 
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 4af6d5fbd..fcdfa8299 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -1074,7 +1074,7 @@ void Controller::fillSignaturesButton() {
 				rpl::single(QString()),
 				[] {},
 				st::manageGroupTopButtonWithText,
-				{ &st::menuIconSigned })));
+				{ &st::menuIconProfile })));
 	profiles->toggleOn(signs->toggledValue());
 	profiles->finishAnimating();
 
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index d2e2e945e..74ef95961 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -166,7 +166,7 @@ PreviewWrap::PreviewWrap(
 		}
 	}, lifetime());
 	session->data().itemViewRefreshRequest(
-	) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
+	) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
 		if (item == _item) {
 			if (goodItem()) {
 				createView();
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 39e41a753..3a52d4c5f 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -544,12 +544,9 @@ auto ChannelData::unavailableReasons() const
 	return _unavailableReasons;
 }
 
-void ChannelData::setUnavailableReasons(
+void ChannelData::setUnavailableReasonsList(
 		std::vector<Data::UnavailableReason> &&reasons) {
-	if (_unavailableReasons != reasons) {
-		_unavailableReasons = std::move(reasons);
-		session().changes().peerUpdated(this, UpdateFlag::UnavailableReason);
-	}
+	_unavailableReasons = std::move(reasons);
 }
 
 void ChannelData::setAvailableMinId(MsgId availableMinId) {
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index 178890107..90f9e2b8d 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -433,9 +433,6 @@ public:
 		return _ptsWaiter.waitingForShortPoll();
 	}
 
-	void setUnavailableReasons(
-		std::vector<Data::UnavailableReason> &&reason);
-
 	[[nodiscard]] MsgId availableMinId() const {
 		return _availableMinId;
 	}
@@ -515,6 +512,9 @@ private:
 		-> const std::vector<Data::UnavailableReason> & override;
 	bool canEditLastAdmin(not_null<UserData*> user) const;
 
+	void setUnavailableReasonsList(
+		std::vector<Data::UnavailableReason> &&reasons) override;
+
 	Flags _flags = ChannelDataFlags(Flag::Forbidden);
 
 	PtsWaiter _ptsWaiter;
diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp
index 29a1899e0..cbd60ff05 100644
--- a/Telegram/SourceFiles/data/data_download_manager.cpp
+++ b/Telegram/SourceFiles/data/data_download_manager.cpp
@@ -143,7 +143,7 @@ void DownloadManager::trackSession(not_null<Main::Session*> session) {
 	}, data.lifetime);
 
 	session->data().itemViewRefreshRequest(
-	) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
+	) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
 		changed(item);
 	}, data.lifetime);
 
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 08aaca699..097cfdfa8 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "data/data_peer.h"
 
+#include "api/api_sensitive_content.h"
 #include "data/data_user.h"
 #include "data/data_chat.h"
 #include "data/data_chat_participant_status.h"
@@ -58,6 +59,11 @@ constexpr auto kUserpicSize = 160;
 
 using UpdateFlag = Data::PeerUpdate::Flag;
 
+[[nodiscard]] const std::vector<QString> &IgnoredReasons(
+		not_null<Main::Session*> session) {
+	return session->appConfig().ignoredRestrictionReasons();
+}
+
 } // namespace
 
 namespace Data {
@@ -85,10 +91,7 @@ UnavailableReason UnavailableReason::Sensitive() {
 QString UnavailableReason::Compute(
 		not_null<Main::Session*> session,
 		const std::vector<UnavailableReason> &list) {
-	const auto &config = session->appConfig();
-	const auto skip = config.get<std::vector<QString>>(
-		"ignore_restriction_reasons",
-		std::vector<QString>());
+	const auto &skip = IgnoredReasons(session);
 	auto &&filtered = ranges::views::all(
 		list
 	) | ranges::views::filter([&](const Data::UnavailableReason &reason) {
@@ -99,6 +102,13 @@ QString UnavailableReason::Compute(
 	return (first != filtered.end()) ? first->text : QString();
 }
 
+bool UnavailableReason::IgnoreSensitiveMark(
+		not_null<Main::Session*> session) {
+	return ranges::contains(
+			IgnoredReasons(session),
+			UnavailableReason::Sensitive().reason);
+}
+
 // We should get a full restriction in "{full}: {reason}" format and we
 // need to find an "-all" tag in {full}, otherwise ignore this restriction.
 std::vector<UnavailableReason> UnavailableReason::Extract(
@@ -554,11 +564,45 @@ QString PeerData::computeUnavailableReason() const {
 		unavailableReasons());
 }
 
-bool PeerData::isUnavailableSensitive() const {
-	return ranges::contains(
-		unavailableReasons(),
+bool PeerData::hasSensitiveContent() const {
+	return _sensitiveContent == 1;
+}
+
+void PeerData::setUnavailableReasonsList(
+		std::vector<Data::UnavailableReason> &&reasons) {
+	Unexpected("PeerData::setUnavailableReasonsList.");
+}
+
+void PeerData::setUnavailableReasons(
+		std::vector<Data::UnavailableReason> &&reasons) {
+	const auto i = ranges::find(
+		reasons,
 		true,
 		&Data::UnavailableReason::sensitive);
+	const auto sensitive = (i != end(reasons));
+	if (sensitive) {
+		reasons.erase(i);
+	}
+	auto changed = (sensitive != hasSensitiveContent());
+	if (changed) {
+		setHasSensitiveContent(sensitive);
+	}
+	if (reasons != unavailableReasons()) {
+		setUnavailableReasonsList(std::move(reasons));
+		changed = true;
+	}
+	if (changed) {
+		session().changes().peerUpdated(
+			this,
+			UpdateFlag::UnavailableReason);
+	}
+}
+
+void PeerData::setHasSensitiveContent(bool has) {
+	_sensitiveContent = has ? 1 : 0;
+	if (has) {
+		session().api().sensitiveContent().preload();
+	}
 }
 
 // This is duplicated in CanPinMessagesValue().
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index 7c393718b..f88d801ce 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -99,6 +99,8 @@ struct UnavailableReason {
 	[[nodiscard]] static QString Compute(
 		not_null<Main::Session*> session,
 		const std::vector<UnavailableReason> &list);
+	[[nodiscard]] static bool IgnoreSensitiveMark(
+		not_null<Main::Session*> session);
 
 	[[nodiscard]] static std::vector<UnavailableReason> Extract(
 		const MTPvector<MTPRestrictionReason> *list);
@@ -347,7 +349,9 @@ public:
 	// If this string is not empty we must not allow to open the
 	// conversation and we must show this string instead.
 	[[nodiscard]] QString computeUnavailableReason() const;
-	[[nodiscard]] bool isUnavailableSensitive() const;
+	[[nodiscard]] bool hasSensitiveContent() const;
+	void setUnavailableReasons(
+		std::vector<Data::UnavailableReason> &&reason);
 
 	[[nodiscard]] ClickHandlerPtr createOpenLink();
 	[[nodiscard]] const ClickHandlerPtr &openLink() {
@@ -489,6 +493,10 @@ private:
 		const ImageLocation &location,
 		bool hasVideo);
 
+	virtual void setUnavailableReasonsList(
+		std::vector<Data::UnavailableReason> &&reasons);
+	void setHasSensitiveContent(bool has);
+
 	const not_null<Data::Session*> _owner;
 
 	mutable Data::CloudImage _userpic;
@@ -507,7 +515,8 @@ private:
 	crl::time _lastFullUpdate = 0;
 
 	QString _name;
-	uint32 _nameVersion : 31 = 1;
+	uint32 _nameVersion : 30 = 1;
+	uint32 _sensitiveContent : 1 = 0;
 	uint32 _wallPaperOverriden : 1 = 0;
 
 	TimeId _ttlPeriod = 0;
diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp
index 4556c2195..0a2d37f8f 100644
--- a/Telegram/SourceFiles/data/data_photo.cpp
+++ b/Telegram/SourceFiles/data/data_photo.cpp
@@ -252,8 +252,7 @@ Image *PhotoData::getReplyPreview(
 
 Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
 	const auto media = item->media();
-	const auto spoiler = (media && media->hasSpoiler())
-		|| item->hasSensitiveSpoiler();
+	const auto spoiler = (media && media->hasSpoiler());
 	return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
 }
 
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 4c2ad3422..8016381b2 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -311,6 +311,22 @@ Session::Session(not_null<Main::Session*> session)
 
 		_stories->loadMore(Data::StorySourcesList::NotHidden);
 	});
+
+	session->appConfig().ignoredRestrictionReasonsChanges(
+	) | rpl::start_with_next([=](std::vector<QString> &&changed) {
+		auto refresh = std::vector<not_null<const HistoryItem*>>();
+		for (const auto &[item, reasons] : _possiblyRestricted) {
+			for (const auto &reason : changed) {
+				if (reasons.contains(reason)) {
+					refresh.push_back(item);
+					break;
+				}
+			}
+		}
+		for (const auto &item : refresh) {
+			requestItemViewRefresh(item);
+		}
+	}, _lifetime);
 }
 
 void Session::subscribeForTopicRepliesLists() {
@@ -1773,7 +1789,7 @@ rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const {
 	return _viewResizeRequest.events();
 }
 
-void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
+void Session::requestItemViewRefresh(not_null<const HistoryItem*> item) {
 	if (const auto view = item->mainView()) {
 		notifyHistoryChangeDelayed(item->history());
 		view->refreshInBlock();
@@ -1781,7 +1797,7 @@ void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
 	_itemViewRefreshRequest.fire_copy(item);
 }
 
-rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const {
+rpl::producer<not_null<const HistoryItem*>> Session::itemViewRefreshRequest() const {
 	return _itemViewRefreshRequest.events();
 }
 
@@ -1807,6 +1823,31 @@ void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
 	}
 }
 
+void Session::registerRestricted(
+		not_null<const HistoryItem*> item,
+		const QString &reason) {
+	Expects(item->hasPossibleRestrictions());
+
+	_possiblyRestricted[item].emplace(reason);
+}
+
+void Session::registerRestricted(
+		not_null<const HistoryItem*> item,
+		const std::vector<UnavailableReason> &reasons) {
+	Expects(item->hasPossibleRestrictions());
+
+	auto &list = _possiblyRestricted[item];
+	if (list.empty()) {
+		auto &&simple = reasons
+			| ranges::views::transform(&UnavailableReason::reason);
+		list = { begin(simple), end(simple) };
+	} else {
+		for (const auto &reason : reasons) {
+			list.emplace(reason.reason);
+		}
+	}
+}
+
 void Session::registerHighlightProcess(
 		uint64 processId,
 		not_null<HistoryItem*> item) {
@@ -2510,6 +2551,9 @@ void Session::unregisterMessage(not_null<HistoryItem*> item) {
 	const auto peerId = item->history()->peer->id;
 	const auto itemId = item->id;
 	_itemRemoved.fire_copy(item);
+	if (item->hasPossibleRestrictions()) {
+		_possiblyRestricted.remove(item);
+	}
 	session().changes().messageUpdated(
 		item,
 		Data::MessageUpdate::Flag::Destroyed);
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 5abb66c98..424a92649 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -69,6 +69,7 @@ class SavedMessages;
 class Chatbots;
 class BusinessInfo;
 struct ReactionId;
+struct UnavailableReason;
 
 struct RepliesReadTillUpdate {
 	FullMsgId id;
@@ -288,8 +289,8 @@ public:
 	[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemResizeRequest() const;
 	void requestViewResize(not_null<ViewElement*> view);
 	[[nodiscard]] rpl::producer<not_null<ViewElement*>> viewResizeRequest() const;
-	void requestItemViewRefresh(not_null<HistoryItem*> item);
-	[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
+	void requestItemViewRefresh(not_null<const HistoryItem*> item);
+	[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemViewRefreshRequest() const;
 	void requestItemTextRefresh(not_null<HistoryItem*> item);
 	void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
 	void notifyHistoryUnloaded(not_null<const History*> history);
@@ -313,6 +314,13 @@ public:
 	void notifyPinnedDialogsOrderUpdated();
 	[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
 
+	void registerRestricted(
+		not_null<const HistoryItem*> item,
+		const QString &reason);
+	void registerRestricted(
+		not_null<const HistoryItem*> item,
+		const std::vector<UnavailableReason> &reasons);
+
 	void registerHighlightProcess(
 		uint64 processId,
 		not_null<HistoryItem*> item);
@@ -920,7 +928,7 @@ private:
 	rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
 	rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
 	rpl::event_stream<not_null<ViewElement*>> _viewResizeRequest;
-	rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest;
+	rpl::event_stream<not_null<const HistoryItem*>> _itemViewRefreshRequest;
 	rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest;
 	rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
 	rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
@@ -1021,6 +1029,10 @@ private:
 	base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
 	base::Timer _pollsClosingTimer;
 
+	base::flat_map<
+		not_null<const HistoryItem*>,
+		base::flat_set<QString>> _possiblyRestricted;
+
 	base::flat_map<FolderId, std::unique_ptr<Folder>> _folders;
 
 	std::unordered_map<
diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h
index 932844085..fbcdf3e3f 100644
--- a/Telegram/SourceFiles/data/data_types.h
+++ b/Telegram/SourceFiles/data/data_types.h
@@ -326,7 +326,7 @@ enum class MessageFlag : uint64 {
 	EffectWatched         = (1ULL << 46),
 
 	SensitiveContent      = (1ULL << 47),
-	AllowSensitive        = (1ULL << 48),
+	HasRestrictions       = (1ULL << 48),
 };
 inline constexpr bool is_flag_type(MessageFlag) { return true; }
 using MessageFlags = base::flags<MessageFlag>;
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index cf369694f..729f27dce 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "data/data_user.h"
 
+#include "api/api_sensitive_content.h"
 #include "storage/localstorage.h"
 #include "storage/storage_user_photos.h"
 #include "main/main_session.h"
@@ -117,14 +118,9 @@ auto UserData::unavailableReasons() const
 	return _unavailableReasons;
 }
 
-void UserData::setUnavailableReasons(
+void UserData::setUnavailableReasonsList(
 		std::vector<Data::UnavailableReason> &&reasons) {
-	if (_unavailableReasons != reasons) {
-		_unavailableReasons = std::move(reasons);
-		session().changes().peerUpdated(
-			this,
-			UpdateFlag::UnavailableReason);
-	}
+	_unavailableReasons = std::move(reasons);
 }
 
 void UserData::setCommonChatsCount(int count) {
@@ -516,6 +512,10 @@ void UserData::setBirthday(Data::Birthday value) {
 	if (_birthday != value) {
 		_birthday = value;
 		session().changes().peerUpdated(this, UpdateFlag::Birthday);
+
+		if (isSelf()) {
+			session().api().sensitiveContent().reload(true);
+		}
 	}
 }
 
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index 8b737c349..d891d4937 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -185,9 +185,6 @@ public:
 	void setBirthday(Data::Birthday value);
 	void setBirthday(const tl::conditional<MTPBirthday> &value);
 
-	void setUnavailableReasons(
-		std::vector<Data::UnavailableReason> &&reasons);
-
 	int commonChatsCount() const;
 	void setCommonChatsCount(int count);
 
@@ -218,6 +215,9 @@ private:
 	auto unavailableReasons() const
 		-> const std::vector<Data::UnavailableReason> & override;
 
+	void setUnavailableReasonsList(
+		std::vector<Data::UnavailableReason> &&reasons) override;
+
 	Flags _flags;
 	Data::LastseenStatus _lastseen;
 	Data::Birthday _birthday;
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 18e803aeb..cebb425ca 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/history_item.h"
 
+#include "api/api_sensitive_content.h"
 #include "lang/lang_keys.h"
 #include "mainwidget.h"
 #include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
@@ -3356,6 +3357,8 @@ EffectId HistoryItem::effectId() const {
 
 QString HistoryItem::computeUnavailableReason() const {
 	if (const auto restrictions = Get<HistoryMessageRestrictions>()) {
+		_flags |= MessageFlag::HasRestrictions;
+		_history->owner().registerRestricted(this, restrictions->reasons);
 		return Data::UnavailableReason::Compute(
 			&history()->session(),
 			restrictions->reasons);
@@ -3363,13 +3366,19 @@ QString HistoryItem::computeUnavailableReason() const {
 	return QString();
 }
 
-bool HistoryItem::hasSensitiveSpoiler() const {
-	return (_flags & MessageFlag::SensitiveContent)
-		&& !(_flags & MessageFlag::AllowSensitive);
+bool HistoryItem::isMediaSensitive() const {
+	if (!(_flags & MessageFlag::SensitiveContent)
+		&& !_history->peer->hasSensitiveContent()) {
+		return false;
+	}
+	_flags |= MessageFlag::HasRestrictions;
+	_history->owner().registerRestricted(this, u"sensitive"_q);
+	return !Data::UnavailableReason::IgnoreSensitiveMark(
+		&_history->session());
 }
 
-void HistoryItem::allowSensitive() {
-	_flags |= MessageFlag::AllowSensitive;
+bool HistoryItem::hasPossibleRestrictions() const {
+	return _flags & MessageFlag::HasRestrictions;
 }
 
 bool HistoryItem::isEmpty() const {
@@ -3666,10 +3675,10 @@ void HistoryItem::createComponents(CreateConfig &&config) {
 			&Data::UnavailableReason::sensitive);
 		if (i != end(restrictions->reasons)) {
 			restrictions->reasons.erase(i);
-			_flags |= MessageFlag::SensitiveContent;
+			flagSensitiveContent();
 		}
 	} else if (!config.restrictions.empty()) {
-		_flags |= MessageFlag::SensitiveContent;
+		flagSensitiveContent();
 	}
 
 	if (out() && isSending()) {
@@ -3679,6 +3688,11 @@ void HistoryItem::createComponents(CreateConfig &&config) {
 	}
 }
 
+void HistoryItem::flagSensitiveContent() {
+	_flags |= MessageFlag::SensitiveContent;
+	_history->session().api().sensitiveContent().preload();
+}
+
 bool HistoryItem::checkRepliesPts(
 		const HistoryMessageRepliesData &data) const {
 	const auto channel = _history->peer->asChannel();
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 6b10b6534..682075e60 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -522,9 +522,9 @@ public:
 	[[nodiscard]] bool isEmpty() const;
 	[[nodiscard]] MessageGroupId groupId() const;
 	[[nodiscard]] EffectId effectId() const;
+	[[nodiscard]] bool hasPossibleRestrictions() const;
 	[[nodiscard]] QString computeUnavailableReason() const;
-	[[nodiscard]] bool hasSensitiveSpoiler() const;
-	void allowSensitive();
+	[[nodiscard]] bool isMediaSensitive() const;
 
 	[[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
 		return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
@@ -660,6 +660,7 @@ private:
 	[[nodiscard]] PreparedServiceText prepareCallScheduledText(
 		TimeId scheduleDate);
 
+	void flagSensitiveContent();
 	[[nodiscard]] PeerData *computeDisplayFrom() const;
 
 	const not_null<History*> _history;
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index cb27621ba..39d5850aa 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -736,6 +736,10 @@ void Element::refreshMedia(Element *replacing) {
 	_flags &= ~Flag::HiddenByGroup;
 
 	const auto item = data();
+	if (!item->computeUnavailableReason().isEmpty()) {
+		_media = nullptr;
+		return;
+	}
 	if (const auto media = item->media()) {
 		if (media->canBeGrouped()) {
 			if (const auto group = history()->owner().groups().find(item)) {
@@ -1004,7 +1008,12 @@ void Element::validateText() {
 			: contextDependentText.links;
 		setTextWithLinks(markedText, customLinks);
 	} else {
-		setTextWithLinks(_textItem->translatedTextWithLocalEntities());
+		const auto unavailable = item->computeUnavailableReason();
+		if (!unavailable.isEmpty()) {
+			setTextWithLinks(Ui::Text::Italic(unavailable));
+		} else {
+			setTextWithLinks(_textItem->translatedTextWithLocalEntities());
+		}
 	}
 }
 
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index ffe0d7757..cfd472f45 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -135,10 +135,13 @@ Gif::Gif(
 , _storyId(realParent->media()
 	? realParent->media()->storyId()
 	: FullStoryId())
-, _spoiler((spoiler || IsHiddenRoundMessage(_parent))
+, _spoiler((spoiler
+	|| IsHiddenRoundMessage(_parent)
+	|| realParent->isMediaSensitive())
 	? std::make_unique<MediaSpoiler>()
 	: nullptr)
-, _downloadSize(Ui::FormatSizeText(_data->size)) {
+, _downloadSize(Ui::FormatSizeText(_data->size))
+, _sensitiveSpoiler(realParent->isMediaSensitive()) {
 	if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
 		if (_spoiler) {
 			_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
@@ -582,9 +585,11 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 		}
 	}
 
-	if (radial
-		|| (!streamingMode
-			&& ((!loaded && !_data->loading()) || !autoplay))) {
+	const auto paintInCenter = !_sensitiveSpoiler
+		&& (radial
+			|| (!streamingMode
+				&& ((!loaded && !_data->loading()) || !autoplay)));
+	if (paintInCenter) {
 		const auto radialRevealed = 1.;
 		const auto opacity = (item->isSending() || _data->uploading())
 			? 1.
@@ -652,6 +657,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 			}
 		}
 		p.setOpacity(1.);
+	} else if (_sensitiveSpoiler) {
+		drawSpoilerTag(p, rthumb, context, [&] {
+			return spoilerTagBackground();
+		});
 	}
 	if (displayMute) {
 		auto muteRect = style::rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width());
@@ -835,6 +844,28 @@ void Gif::paintTranscribe(
 		context);
 }
 
+void Gif::drawSpoilerTag(
+		Painter &p,
+		QRect rthumb,
+		const PaintContext &context,
+		Fn<QImage()> generateBackground) const {
+	Media::drawSpoilerTag(
+		p,
+		_spoiler.get(),
+		_spoilerTag,
+		rthumb,
+		context,
+		std::move(generateBackground));
+}
+
+ClickHandlerPtr Gif::spoilerTagLink() const {
+	return Media::spoilerTagLink(_spoiler.get(), _spoilerTag);
+}
+
+QImage Gif::spoilerTagBackground() const {
+	return _spoiler ? _spoiler->background : QImage();
+}
+
 void Gif::validateVideoThumbnail() const {
 	const auto content = _dataMedia->videoThumbnailContent();
 	if (_videoThumbnailFrame || content.isEmpty()) {
@@ -1113,10 +1144,12 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
 	}
 	if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
 		ensureDataMediaCreated();
-		result.link = (isRound && _parent->data()->media()->ttlSeconds())
-			? _openl // Overriden.
-			: (_spoiler && !_spoiler->revealed)
-			? _spoiler->link
+		result.link = (_spoiler && !_spoiler->revealed)
+			? (_sensitiveSpoiler
+				? spoilerTagLink()
+				: (isRound && _parent->data()->media()->ttlSeconds())
+				? _openl // Overriden.
+				: _spoiler->link)
 			: _data->uploading()
 			? _cancell
 			: _realParent->isSending()
@@ -1335,9 +1368,11 @@ void Gif::drawGrouped(
 		p.setOpacity(1.);
 	}
 
-	if (radial
-		|| (!streamingMode
-			&& ((!loaded && !_data->loading()) || !autoplay))) {
+	const auto paintInCenter = !_sensitiveSpoiler
+		&& (radial
+			|| (!streamingMode
+				&& ((!loaded && !_data->loading()) || !autoplay)));
+	if (paintInCenter) {
 		const auto radialRevealed = 1.;
 		const auto opacity = (item->isSending() || _data->uploading())
 			? 1.
@@ -1439,8 +1474,8 @@ TextState Gif::getStateGrouped(
 		}
 	}
 	ensureDataMediaCreated();
-	return TextState(_parent, (_spoiler && !_spoiler->revealed)
-		? _spoiler->link
+	auto link = (_spoiler && !_spoiler->revealed)
+		? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
 		: _data->uploading()
 		? _cancell
 		: _realParent->isSending()
@@ -1449,7 +1484,8 @@ TextState Gif::getStateGrouped(
 		? _openl
 		: _data->loading()
 		? _cancell
-		: _savel);
+		: _savel;
+	return TextState(_parent, std::move(link));
 }
 
 void Gif::ensureDataMediaCreated() const {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h
index f86421f92..45edc9ce3 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h
@@ -90,6 +90,14 @@ public:
 	void stopAnimation() override;
 	void checkAnimation() override;
 
+	void drawSpoilerTag(
+		Painter &p,
+		QRect rthumb,
+		const PaintContext &context,
+		Fn<QImage()> generateBackground) const override;
+	ClickHandlerPtr spoilerTagLink() const override;
+	QImage spoilerTagBackground() const override;
+
 	void hideSpoilers() override;
 	bool needsBubble() const override;
 	bool unwrapped() const override;
@@ -203,6 +211,7 @@ private:
 	const FullStoryId _storyId;
 	std::unique_ptr<Streamed> _streamed;
 	const std::unique_ptr<MediaSpoiler> _spoiler;
+	mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
 	mutable std::unique_ptr<TranscribeButton> _transcribe;
 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
 	mutable std::unique_ptr<Image> _videoThumbnailFrame;
@@ -214,6 +223,7 @@ private:
 	mutable bool _thumbIsEllipse : 1 = false;
 	mutable bool _pollingStory : 1 = false;
 	mutable bool _purchasedPriceTag : 1 = false;
+	const bool _sensitiveSpoiler : 1 = false;
 
 };
 
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp
index ddd76f945..2fc977402 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp
@@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/history_view_text_helper.h"
-#include "history/view/media/history_view_sticker.h"
+#include "history/view/media/history_view_media_common.h"
 #include "history/view/media/history_view_media_spoiler.h"
+#include "history/view/media/history_view_sticker.h"
 #include "storage/storage_shared_media.h"
 #include "data/data_document.h"
 #include "data/data_session.h"
 #include "data/data_web_page.h"
-#include "lang/lang_tag.h" // FormatCountDecimal.
+#include "lang/lang_keys.h"
 #include "ui/item_text_options.h"
 #include "ui/chat/chat_style.h"
 #include "ui/chat/message_bubble.h"
@@ -31,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/text/text_utilities.h"
 #include "core/ui_integration.h"
 #include "styles/style_chat.h"
+#include "styles/style_chat_helpers.h"
+#include "styles/style_menu_icons.h" // mediaMenuIconStealth.
 
 namespace HistoryView {
 namespace {
@@ -351,6 +354,156 @@ void Media::fillImageSpoiler(
 		spoiler->cornerCache);
 }
 
+void Media::drawSpoilerTag(
+		Painter &p,
+		not_null<MediaSpoiler*> spoiler,
+		std::unique_ptr<MediaSpoilerTag> &tag,
+		QRect rthumb,
+		const PaintContext &context,
+		Fn<QImage()> generateBackground) const {
+	if (!tag) {
+		setupSpoilerTag(tag);
+		if (!tag) {
+			return;
+		}
+	}
+	const auto revealed = spoiler->revealAnimation.value(
+		spoiler->revealed ? 1. : 0.);
+	if (revealed == 1.) {
+		return;
+	}
+	p.setOpacity(1. - revealed);
+	const auto st = context.st;
+	const auto darken = st->msgDateImgBg()->c;
+	const auto fg = st->msgDateImgFg()->c;
+	const auto star = st->creditsBg1()->c;
+	if (tag->cache.isNull()
+		|| tag->darken != darken
+		|| tag->fg != fg
+		|| tag->star != star) {
+		const auto ratio = style::DevicePixelRatio();
+		auto bg = generateBackground();
+		if (bg.isNull()) {
+			bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
+			bg.fill(Qt::black);
+		}
+
+		auto text = Ui::Text::String();
+		auto iconSkip = 0;
+		if (tag->sensitive) {
+			text.setText(
+				st::semiboldTextStyle,
+				tr::lng_sensitive_tag(tr::now));
+			iconSkip = st::mediaMenuIconStealth.width() * 1.4;
+		} else {
+			const auto session = &history()->session();
+			auto price = Ui::Text::Colorized(Ui::CreditsEmoji(session));
+			price.append(Lang::FormatCountDecimal(tag->price));
+			text.setMarkedText(
+				st::semiboldTextStyle,
+				tr::lng_paid_price(
+					tr::now,
+					lt_price,
+					price,
+					Ui::Text::WithEntities),
+				kMarkupTextOptions,
+				Core::MarkedTextContext{
+					.session = session,
+					.customEmojiRepaint = [] {},
+				});
+		}
+		const auto width = iconSkip + text.maxWidth();
+		const auto inner = QRect(0, 0, width, text.minHeight());
+		const auto outer = inner.marginsAdded(st::paidTagPadding);
+		const auto size = outer.size();
+		const auto radius = std::min(size.width(), size.height()) / 2;
+		auto cache = QImage(
+			size * ratio,
+			QImage::Format_ARGB32_Premultiplied);
+		cache.setDevicePixelRatio(ratio);
+		cache.fill(Qt::black);
+		auto p = Painter(&cache);
+		auto hq = PainterHighQualityEnabler(p);
+		p.drawImage(
+			QRect(
+				(size.width() - rthumb.width()) / 2,
+				(size.height() - rthumb.height()) / 2,
+				rthumb.width(),
+				rthumb.height()),
+			bg);
+		p.fillRect(QRect(QPoint(), size), darken);
+		p.setPen(fg);
+		p.setTextPalette(st->priceTagTextPalette());
+		if (iconSkip) {
+			st::mediaMenuIconStealth.paint(
+				p,
+				-outer.x(),
+				(size.height() - st::mediaMenuIconStealth.height()) / 2,
+				size.width(),
+				fg);
+		}
+		text.draw(p, iconSkip - outer.x(), -outer.y(), width);
+		p.end();
+
+		tag->darken = darken;
+		tag->fg = fg;
+		tag->cache = Images::Round(
+			std::move(cache),
+			Images::CornersMask(radius));
+	}
+	const auto &cache = tag->cache;
+	const auto size = cache.size() / cache.devicePixelRatio();
+	const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2;
+	const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2;
+	p.drawImage(left, top, cache);
+	if (context.selected()) {
+		auto hq = PainterHighQualityEnabler(p);
+		const auto radius = std::min(size.width(), size.height()) / 2;
+		p.setPen(Qt::NoPen);
+		p.setBrush(st->msgSelectOverlay());
+		p.drawRoundedRect(
+			QRect(left, top, size.width(), size.height()),
+			radius,
+			radius);
+	}
+	p.setOpacity(1.);
+}
+
+void Media::setupSpoilerTag(std::unique_ptr<MediaSpoilerTag> &tag) const {
+	const auto item = parent()->data();
+	if (item->isMediaSensitive()) {
+		tag = std::make_unique<MediaSpoilerTag>();
+		tag->sensitive = 1;
+		return;
+	}
+	const auto media = parent()->data()->media();
+	const auto invoice = media ? media->invoice() : nullptr;
+	if (const auto price = invoice->isPaidMedia ? invoice->amount : 0) {
+		tag = std::make_unique<MediaSpoilerTag>();
+		tag->price = price;
+	}
+}
+
+ClickHandlerPtr Media::spoilerTagLink(
+		not_null<MediaSpoiler*> spoiler,
+		std::unique_ptr<MediaSpoilerTag> &tag) const {
+	const auto item = parent()->data();
+	if (!item->isRegular() || spoiler->revealed) {
+		return nullptr;
+	} else if (!tag) {
+		setupSpoilerTag(tag);
+		if (!tag) {
+			return nullptr;
+		}
+	}
+	if (!tag->link) {
+		tag->link = tag->sensitive
+			? MakeSensitiveMediaLink(spoiler->link, item)
+			: MakePaidMediaLink(item);
+	}
+	return tag->link;
+}
+
 void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
 	const auto weak = base::make_weak(this);
 	spoiler->link = std::make_shared<LambdaClickHandler>([weak, spoiler](
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h
index 94ad6520b..93c877b66 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media.h
@@ -47,6 +47,7 @@ enum class InfoDisplayType : char;
 struct TextState;
 struct StateRequest;
 struct MediaSpoiler;
+struct MediaSpoilerTag;
 class StickerPlayer;
 class Element;
 struct SelectedQuote;
@@ -223,18 +224,18 @@ public:
 		QPoint point,
 		StateRequest request) const;
 
-	virtual void drawPriceTag(
+	virtual void drawSpoilerTag(
 			Painter &p,
 			QRect rthumb,
 			const PaintContext &context,
 			Fn<QImage()> generateBackground) const {
-		Unexpected("Price tag method call.");
+		Unexpected("Spoiler tag method call.");
 	}
-	[[nodiscard]] virtual ClickHandlerPtr priceTagLink() const {
-		Unexpected("Price tag method call.");
+	[[nodiscard]] virtual ClickHandlerPtr spoilerTagLink() const {
+		Unexpected("Spoiler tag method call.");
 	}
-	[[nodiscard]] virtual QImage priceTagBackground() const {
-		Unexpected("Price tag method call.");
+	[[nodiscard]] virtual QImage spoilerTagBackground() const {
+		Unexpected("Spoiler tag method call.");
 	}
 
 	[[nodiscard]] virtual bool animating() const {
@@ -390,6 +391,17 @@ protected:
 		not_null<MediaSpoiler*> spoiler,
 		QRect rect,
 		const PaintContext &context) const;
+	void drawSpoilerTag(
+		Painter &p,
+		not_null<MediaSpoiler*> spoiler,
+		std::unique_ptr<MediaSpoilerTag> &tag,
+		QRect rthumb,
+		const PaintContext &context,
+		Fn<QImage()> generateBackground) const;
+	void setupSpoilerTag(std::unique_ptr<MediaSpoilerTag> &tag) const;
+	[[nodiscard]] ClickHandlerPtr spoilerTagLink(
+		not_null<MediaSpoiler*> spoiler,
+		std::unique_ptr<MediaSpoilerTag> &tag) const;
 	void createSpoilerLink(not_null<MediaSpoiler*> spoiler);
 
 	void repaint() const;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
index 245f4ceb9..b899d9fc9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
@@ -7,10 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/media/history_view_media_common.h"
 
+#include "api/api_sensitive_content.h"
 #include "api/api_views.h"
 #include "apiwrap.h"
+#include "ui/boxes/confirm_box.h"
+#include "ui/layers/generic_box.h"
 #include "ui/text/format_values.h"
 #include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/wrap/slide_wrap.h"
 #include "ui/painter.h"
 #include "core/click_handler_types.h"
 #include "data/data_document.h"
@@ -272,4 +278,62 @@ ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) {
 	});
 }
 
+ClickHandlerPtr MakeSensitiveMediaLink(
+		ClickHandlerPtr reveal,
+		not_null<HistoryItem*> item) {
+	const auto session = &item->history()->session();
+	return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
+		const auto my = context.other.value<ClickHandlerContext>();
+		const auto controller = my.sessionWindow.get();
+		const auto show = controller ? controller->uiShow() : my.show;
+		if (!show) {
+			reveal->onClick(context);
+			return;
+		}
+		show->show(Box([=](not_null<Ui::GenericBox*> box) {
+			struct State {
+				rpl::variable<bool> canChange;
+				Ui::Checkbox *checkbox = nullptr;
+			};
+			const auto state = box->lifetime().make_state<State>();
+			const auto sensitive = &session->api().sensitiveContent();
+			state->canChange = sensitive->canChange();
+			const auto done = [=](Fn<void()> close) {
+				if (state->canChange.current()
+					&& state->checkbox->checked()) {
+					show->showToast({
+						.text = tr::lng_sensitive_toast(
+							tr::now,
+							Ui::Text::RichLangValue),
+						.adaptive = true,
+						.duration = 5 * crl::time(1000),
+					});
+					sensitive->update(true);
+				} else {
+					reveal->onClick(context);
+				}
+				close();
+			};
+			Ui::ConfirmBox(box, {
+				.text = tr::lng_sensitive_text(Ui::Text::RichLangValue),
+				.confirmed = done,
+				.confirmText = tr::lng_sensitive_view(),
+				.title = tr::lng_sensitive_title(),
+			});
+			const auto skip = st::defaultCheckbox.margin.bottom();
+			const auto wrap = box->addRow(
+				object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
+					box,
+					object_ptr<Ui::Checkbox>(
+						box,
+						tr::lng_sensitive_always(tr::now),
+						false)),
+				st::boxRowPadding + QMargins(0, 0, 0, skip));
+			wrap->toggleOn(state->canChange.value());
+			wrap->finishAnimating();
+			state->checkbox = wrap->entity();
+		}));
+	});
+}
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.h b/Telegram/SourceFiles/history/view/media/history_view_media_common.h
index a37d0ab62..03646372f 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_common.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.h
@@ -75,6 +75,10 @@ void PaintInterpolatedIcon(
 	int newWidth,
 	int maxWidth);
 
-[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item);
+[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(
+	not_null<HistoryItem*> item);
+[[nodiscard]] ClickHandlerPtr MakeSensitiveMediaLink(
+	ClickHandlerPtr reveal,
+	not_null<HistoryItem*> item);
 
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
index 4b26222ee..5cf80255f 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
@@ -297,16 +297,19 @@ QMargins GroupedMedia::groupedPadding() const {
 		(normal.bottom() - grouped.bottom()) + addToBottom);
 }
 
-Media *GroupedMedia::lookupUnpaidMedia() const {
+Media *GroupedMedia::lookupSpoilerTagMedia() const {
 	if (_parts.empty()) {
 		return nullptr;
 	}
 	const auto media = _parts.front().content.get();
+	if (media && _parts.front().item->isMediaSensitive()) {
+		return media;
+	}
 	const auto photo = media ? media->getPhoto() : nullptr;
 	return (photo && photo->extendedMediaPreview()) ? media : nullptr;
 }
 
-QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
+QImage GroupedMedia::generateSpoilerTagBackground(QRect full) const {
 	const auto ratio = style::DevicePixelRatio();
 	auto result = QImage(
 		full.size() * ratio,
@@ -317,7 +320,7 @@ QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
 	const auto skip1 = st::historyGroupSkip / 2;
 	const auto skip2 = st::historyGroupSkip - skip1;
 	for (const auto &part : _parts) {
-		auto background = part.content->priceTagBackground();
+		auto background = part.content->spoilerTagBackground();
 		const auto extended = part.geometry.translated(shift).marginsAdded(
 			{ skip1, skip1, skip2, skip2 });
 		if (background.isNull()) {
@@ -394,7 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
 		? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
 		: adjustedBubbleRounding();
 	auto highlight = context.highlight.range;
-	const auto unpaid = lookupUnpaidMedia();
+	const auto tagged = lookupSpoilerTagMedia();
 	auto fullRect = QRect();
 	const auto subpartHighlight = IsSubGroupSelection(highlight);
 	for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
@@ -435,7 +438,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
 		if (!part.cache.isNull()) {
 			nowCache = true;
 		}
-		if (unpaid || _purchasedPriceTag) {
+		if (tagged || _purchasedPriceTag) {
 			fullRect = fullRect.united(part.geometry);
 		}
 	}
@@ -443,9 +446,9 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
 		history()->owner().registerHeavyViewPart(_parent);
 	}
 
-	if (unpaid) {
-		unpaid->drawPriceTag(p, fullRect, context, [&] {
-			return generatePriceTagBackground(fullRect);
+	if (tagged) {
+		tagged->drawSpoilerTag(p, fullRect, context, [&] {
+			return generateSpoilerTagBackground(fullRect);
 		});
 	} else if (_purchasedPriceTag) {
 		drawPurchasedTag(p, fullRect, context);
@@ -511,9 +514,11 @@ PointState GroupedMedia::pointState(QPoint point) const {
 TextState GroupedMedia::textState(QPoint point, StateRequest request) const {
 	const auto groupPadding = groupedPadding();
 	auto result = getPartState(point - QPoint(0, groupPadding.top()), request);
-	if (const auto unpaid = lookupUnpaidMedia()) {
+	if (const auto tagged = lookupSpoilerTagMedia()) {
 		if (QRect(0, 0, width(), height()).contains(point)) {
-			result.link = unpaid->priceTagLink();
+			if (auto link = tagged->spoilerTagLink()) {
+				result.link = std::move(link);
+			}
 		}
 	}
 	if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
index 5397b6817..459eb4b1c 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
@@ -149,8 +149,8 @@ private:
 		RectParts sides) const;
 	[[nodiscard]] QMargins groupedPadding() const;
 
-	[[nodiscard]] Media *lookupUnpaidMedia() const;
-	[[nodiscard]] QImage generatePriceTagBackground(QRect full) const;
+	[[nodiscard]] Media *lookupSpoilerTagMedia() const;
+	[[nodiscard]] QImage generateSpoilerTagBackground(QRect full) const;
 
 	mutable std::optional<HistoryItem*> _captionItem;
 	std::vector<Part> _parts;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h
index 2b885a25a..f668a2fd9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h
@@ -26,4 +26,14 @@ struct MediaSpoiler {
 	bool revealed = false;
 };
 
+struct MediaSpoilerTag {
+	uint64 price : 63 = 0;
+	uint64 sensitive : 1 = 0;
+	QImage cache;
+	QColor darken;
+	QColor fg;
+	QColor star;
+	ClickHandlerPtr link;
+};
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index 043883035..a8a5f6da9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -63,15 +63,6 @@ struct Photo::Streamed {
 	QImage roundingMask;
 };
 
-struct Photo::PriceTag {
-	uint64 price = 0;
-	QImage cache;
-	QColor darken;
-	QColor fg;
-	QColor star;
-	ClickHandlerPtr link;
-};
-
 Photo::Streamed::Streamed(
 	std::shared_ptr<::Media::Streaming::Document> shared)
 : instance(std::move(shared), nullptr) {
@@ -87,7 +78,10 @@ Photo::Photo(
 , _storyId(realParent->media()
 	? realParent->media()->storyId()
 	: FullStoryId())
-, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) {
+, _spoiler((spoiler || realParent->isMediaSensitive())
+	? std::make_unique<MediaSpoiler>()
+	: nullptr)
+, _sensitiveSpoiler(realParent->isMediaSensitive() ? 1 : 0) {
 	create(realParent->fullId());
 }
 
@@ -342,7 +336,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 	}
 
 	const auto showEnlarge = loaded && _showEnlarge;
-	const auto paintInCenter = (radial || (!loaded && !_data->loading()));
+	const auto paintInCenter = !_sensitiveSpoiler
+		&& (radial || (!loaded && !_data->loading()));
 	if (paintInCenter || showEnlarge) {
 		p.setPen(Qt::NoPen);
 		if (context.selected()) {
@@ -382,9 +377,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
 			_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
 		}
-	} else if (preview) {
-		drawPriceTag(p, rthumb, context, [&] {
-			return priceTagBackground();
+	} else if (_sensitiveSpoiler || preview) {
+		drawSpoilerTag(p, rthumb, context, [&] {
+			return spoilerTagBackground();
 		});
 	}
 	if (showEnlarge) {
@@ -426,105 +421,18 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 	}
 }
 
-void Photo::setupPriceTag() const {
-	const auto media = parent()->data()->media();
-	const auto invoice = media ? media->invoice() : nullptr;
-	const auto price = invoice->isPaidMedia ? invoice->amount : 0;
-	if (!price) {
-		return;
-	}
-	_priceTag = std::make_unique<PriceTag>();
-	_priceTag->price = price;
-}
-
-void Photo::drawPriceTag(
+void Photo::drawSpoilerTag(
 		Painter &p,
 		QRect rthumb,
 		const PaintContext &context,
 		Fn<QImage()> generateBackground) const {
-	if (!_priceTag) {
-		setupPriceTag();
-		if (!_priceTag) {
-			return;
-		}
-	}
-	const auto st = context.st;
-	const auto darken = st->msgDateImgBg()->c;
-	const auto fg = st->msgDateImgFg()->c;
-	const auto star = st->creditsBg1()->c;
-	if (_priceTag->cache.isNull()
-		|| _priceTag->darken != darken
-		|| _priceTag->fg != fg
-		|| _priceTag->star != star) {
-		const auto ratio = style::DevicePixelRatio();
-		auto bg = generateBackground();
-		if (bg.isNull()) {
-			bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
-			bg.fill(Qt::black);
-		}
-
-		auto text = Ui::Text::String();
-		const auto session = &history()->session();
-		auto price = Ui::Text::Colorized(Ui::CreditsEmoji(session));
-		price.append(Lang::FormatCountDecimal(_priceTag->price));
-		text.setMarkedText(
-			st::semiboldTextStyle,
-			tr::lng_paid_price(
-				tr::now,
-				lt_price,
-				price,
-				Ui::Text::WithEntities),
-			kMarkupTextOptions,
-			Core::MarkedTextContext{
-				.session = session,
-				.customEmojiRepaint = [] {},
-			});
-		const auto width = text.maxWidth();
-		const auto inner = QRect(0, 0, width, text.minHeight());
-		const auto outer = inner.marginsAdded(st::paidTagPadding);
-		const auto size = outer.size();
-		const auto radius = std::min(size.width(), size.height()) / 2;
-		auto cache = QImage(
-			size * ratio,
-			QImage::Format_ARGB32_Premultiplied);
-		cache.setDevicePixelRatio(ratio);
-		cache.fill(Qt::black);
-		auto p = Painter(&cache);
-		auto hq = PainterHighQualityEnabler(p);
-		p.drawImage(
-			QRect(
-				(size.width() - rthumb.width()) / 2,
-				(size.height() - rthumb.height()) / 2,
-				rthumb.width(),
-				rthumb.height()),
-			bg);
-		p.fillRect(QRect(QPoint(), size), darken);
-		p.setPen(fg);
-		p.setTextPalette(st->priceTagTextPalette());
-		text.draw(p, -outer.x(), -outer.y(), width);
-		p.end();
-
-		_priceTag->darken = darken;
-		_priceTag->fg = fg;
-		_priceTag->cache = Images::Round(
-			std::move(cache),
-			Images::CornersMask(radius));
-	}
-	const auto &cache = _priceTag->cache;
-	const auto size = cache.size() / cache.devicePixelRatio();
-	const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2;
-	const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2;
-	p.drawImage(left, top, cache);
-	if (context.selected()) {
-		auto hq = PainterHighQualityEnabler(p);
-		const auto radius = std::min(size.width(), size.height()) / 2;
-		p.setPen(Qt::NoPen);
-		p.setBrush(st->msgSelectOverlay());
-		p.drawRoundedRect(
-			QRect(left, top, size.width(), size.height()),
-			radius,
-			radius);
-	}
+	Media::drawSpoilerTag(
+		p,
+		_spoiler.get(),
+		_spoilerTag,
+		rthumb,
+		context,
+		std::move(generateBackground));
 }
 
 void Photo::validateUserpicImageCache(QSize size, bool forum) const {
@@ -734,23 +642,11 @@ QRect Photo::enlargeRect() const {
 	};
 }
 
-ClickHandlerPtr Photo::priceTagLink() const {
-	const auto item = parent()->data();
-	if (!item->isRegular()) {
-		return nullptr;
-	} else if (!_priceTag) {
-		setupPriceTag();
-		if (!_priceTag) {
-			return nullptr;
-		}
-	}
-	if (!_priceTag->link) {
-		_priceTag->link = MakePaidMediaLink(item);
-	}
-	return _priceTag->link;
+ClickHandlerPtr Photo::spoilerTagLink() const {
+	return Media::spoilerTagLink(_spoiler.get(), _spoilerTag);
 }
 
-QImage Photo::priceTagBackground() const {
+QImage Photo::spoilerTagBackground() const {
 	return _spoiler ? _spoiler->background : QImage();
 }
 
@@ -767,10 +663,10 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
 
 	if (QRect(paintx, painty, paintw, painth).contains(point)) {
 		ensureDataMediaCreated();
-		result.link = _data->extendedMediaPreview()
-			? priceTagLink()
-			: (_spoiler && !_spoiler->revealed)
-			? _spoiler->link
+		result.link = (_spoiler && !_spoiler->revealed)
+			? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
+				? spoilerTagLink()
+				: _spoiler->link)
 			: _data->uploading()
 			? _cancell
 			: _dataMedia->loaded()
@@ -875,10 +771,11 @@ void Photo::drawGrouped(
 		p.setOpacity(1.);
 	}
 
-	const auto displayState = radial
-		|| (!loaded && !_data->loading())
-		|| _data->waitingForAlbum();
-	if (displayState) {
+	const auto paintInCenter = !_sensitiveSpoiler
+		&& (radial
+			|| (!loaded && !_data->loading())
+			|| _data->waitingForAlbum());
+	if (paintInCenter) {
 		const auto radialOpacity = radial
 			? _animation->radial.opacity()
 			: 1.;
@@ -941,17 +838,18 @@ TextState Photo::getStateGrouped(
 		return {};
 	}
 	ensureDataMediaCreated();
-	return TextState(_parent, _data->extendedMediaPreview()
-		? priceTagLink()
-		: (_spoiler && !_spoiler->revealed)
-		? _spoiler->link
+	auto link = (_spoiler && !_spoiler->revealed)
+		? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
+			? spoilerTagLink()
+			: _spoiler->link)
 		: _data->uploading()
 		? _cancell
 		: _dataMedia->loaded()
 		? _openl
 		: _data->loading()
 		? _cancell
-		: _savel);
+		: _savel;
+	return TextState(_parent, std::move(link));
 }
 
 float64 Photo::dataProgress() const {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index 3c1aec3e4..710901f76 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -75,13 +75,13 @@ public:
 		QPoint point,
 		StateRequest request) const override;
 
-	void drawPriceTag(
+	void drawSpoilerTag(
 		Painter &p,
 		QRect rthumb,
 		const PaintContext &context,
 		Fn<QImage()> generateBackground) const override;
-	ClickHandlerPtr priceTagLink() const override;
-	QImage priceTagBackground() const override;
+	ClickHandlerPtr spoilerTagLink() const override;
+	QImage spoilerTagBackground() const override;
 
 	void hideSpoilers() override;
 	bool needsBubble() const override;
@@ -105,7 +105,6 @@ protected:
 
 private:
 	struct Streamed;
-	struct PriceTag;
 
 	void create(FullMsgId contextId, PeerData *chat = nullptr);
 
@@ -115,7 +114,7 @@ private:
 
 	void ensureDataMediaCreated() const;
 	void dataMediaCreated() const;
-	void setupPriceTag() const;
+	void setupSpoilerTag() const;
 
 	QSize countOptimalSize() override;
 	QSize countCurrentSize(int newWidth) override;
@@ -161,14 +160,15 @@ private:
 
 	const not_null<PhotoData*> _data;
 	const FullStoryId _storyId;
-	mutable std::unique_ptr<PriceTag> _priceTag;
 	mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
 	mutable std::unique_ptr<Streamed> _streamed;
 	const std::unique_ptr<MediaSpoiler> _spoiler;
+	mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
 	mutable QImage _imageCache;
 	mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
-	uint32 _serviceWidth : 27 = 0;
+	uint32 _serviceWidth : 26 = 0;
 	uint32 _purchasedPriceTag : 1 = 0;
+	const uint32 _sensitiveSpoiler : 1 = 0;
 	mutable uint32 _imageCacheForum : 1 = 0;
 	mutable uint32 _imageCacheBlurred : 1 = 0;
 	mutable uint32 _pollingStory : 1 = 0;
diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp
index 6a4c63c35..11e81958c 100644
--- a/Telegram/SourceFiles/main/main_app_config.cpp
+++ b/Telegram/SourceFiles/main/main_app_config.cpp
@@ -38,15 +38,18 @@ void AppConfig::start() {
 	}, _lifetime);
 }
 
-void AppConfig::refresh() {
+void AppConfig::refresh(bool force) {
 	if (_requestId || !_api) {
+		if (force) {
+			_pendingRefresh = true;
+		}
 		return;
 	}
+	_pendingRefresh = false;
 	_requestId = _api->request(MTPhelp_GetAppConfig(
 		MTP_int(_hash)
 	)).done([=](const MTPhelp_AppConfig &result) {
 		_requestId = 0;
-		refreshDelayed();
 		result.match([&](const MTPDhelp_appConfig &data) {
 			_hash = data.vhash().v;
 
@@ -55,15 +58,25 @@ void AppConfig::refresh() {
 				LOG(("API Error: Unexpected config type."));
 				return;
 			}
+			auto was = ignoredRestrictionReasons();
+
 			_data.clear();
 			for (const auto &element : config.c_jsonObject().vvalue().v) {
 				element.match([&](const MTPDjsonObjectValue &data) {
 					_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
 				});
 			}
+			updateIgnoredRestrictionReasons(std::move(was));
+
 			DEBUG_LOG(("getAppConfig result handled."));
 			_refreshed.fire({});
 		}, [](const MTPDhelp_appConfigNotModified &) {});
+
+		if (base::take(_pendingRefresh)) {
+			refresh();
+		} else {
+			refreshDelayed();
+		}
 	}).fail([=] {
 		_requestId = 0;
 		refreshDelayed();
@@ -76,6 +89,24 @@ void AppConfig::refreshDelayed() {
 	});
 }
 
+void AppConfig::updateIgnoredRestrictionReasons(std::vector<QString> was) {
+	_ignoreRestrictionReasons = get<std::vector<QString>>(
+		u"ignore_restriction_reasons"_q,
+		std::vector<QString>());
+	ranges::sort(_ignoreRestrictionReasons);
+	if (_ignoreRestrictionReasons != was) {
+		for (const auto &reason : _ignoreRestrictionReasons) {
+			const auto i = ranges::remove(was, reason);
+			if (i != end(was)) {
+				was.erase(i, end(was));
+			} else {
+				was.push_back(reason);
+			}
+		}
+		_ignoreRestrictionChanges.fire(std::move(was));
+	}
+}
+
 rpl::producer<> AppConfig::refreshed() const {
 	return _refreshed.events();
 }
diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h
index f09d5f120..09fe65ea1 100644
--- a/Telegram/SourceFiles/main/main_app_config.h
+++ b/Telegram/SourceFiles/main/main_app_config.h
@@ -55,7 +55,15 @@ public:
 
 	[[nodiscard]] bool newRequirePremiumFree() const;
 
-	void refresh();
+	[[nodiscard]] auto ignoredRestrictionReasons() const
+		-> const std::vector<QString> & {
+		return _ignoreRestrictionReasons;
+	}
+	[[nodiscard]] auto ignoredRestrictionReasonsChanges() const {
+		return _ignoreRestrictionChanges.events();
+	}
+
+	void refresh(bool force = false);
 
 private:
 	void refreshDelayed();
@@ -84,14 +92,20 @@ private:
 		const QString &key,
 		std::vector<int> &&fallback) const;
 
+	void updateIgnoredRestrictionReasons(std::vector<QString> was);
+
 	const not_null<Account*> _account;
 	std::optional<MTP::Sender> _api;
 	mtpRequestId _requestId = 0;
 	int32 _hash = 0;
+	bool _pendingRefresh = false;
 	base::flat_map<QString, MTPJSONValue> _data;
 	rpl::event_stream<> _refreshed;
 	base::flat_set<QString> _dismissedSuggestions;
 
+	std::vector<QString> _ignoreRestrictionReasons;
+	rpl::event_stream<std::vector<QString>> _ignoreRestrictionChanges;
+
 	rpl::lifetime _lifetime;
 
 };
diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp
index 9d6203c13..3ddc6d8f5 100644
--- a/Telegram/SourceFiles/settings/settings_chat.cpp
+++ b/Telegram/SourceFiles/settings/settings_chat.cpp
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "settings/settings_chat.h"
 
+#include "base/timer_rpl.h"
 #include "settings/settings_advanced.h"
+#include "settings/settings_privacy_security.h"
 #include "settings/settings_experimental.h"
 #include "boxes/abstract_box.h"
 #include "boxes/peers/edit_peer_color_box.h"
@@ -1022,7 +1024,6 @@ void SetupMessages(
 void SetupArchive(
 		not_null<Window::SessionController*> controller,
 		not_null<Ui::VerticalLayout*> container) {
-	Ui::AddDivider(container);
 	Ui::AddSkip(container);
 
 	PreloadArchiveSettings(&controller->session());
@@ -1801,12 +1802,17 @@ void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
 void Chat::setupContent(not_null<Window::SessionController*> controller) {
 	const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
 
+	auto updateOnTick = rpl::single(
+	) | rpl::then(base::timer_each(60 * crl::time(1000)));
+
 	SetupThemeOptions(controller, content);
 	SetupThemeSettings(controller, content);
 	SetupCloudThemes(controller, content);
 	SetupChatBackground(controller, content);
 	SetupStickersEmoji(controller, content);
 	SetupMessages(controller, content);
+	Ui::AddDivider(content);
+	SetupSensitiveContent(controller, content, std::move(updateOnTick));
 	SetupArchive(controller, content);
 
 	Ui::ResizeFitChild(this, content);
diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
index 13dae9fb6..f71134c3c 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
@@ -577,47 +577,6 @@ void SetupTopPeers(
 	Ui::AddDividerText(container, tr::lng_settings_top_peers_about());
 }
 
-void SetupSensitiveContent(
-		not_null<Window::SessionController*> controller,
-		not_null<Ui::VerticalLayout*> container,
-		rpl::producer<> updateTrigger) {
-	using namespace rpl::mappers;
-
-	const auto wrap = container->add(
-		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
-			container,
-			object_ptr<Ui::VerticalLayout>(container)));
-	const auto inner = wrap->entity();
-
-	Ui::AddSkip(inner);
-	Ui::AddSubsectionTitle(inner, tr::lng_settings_sensitive_title());
-
-	const auto session = &controller->session();
-
-	std::move(
-		updateTrigger
-	) | rpl::start_with_next([=] {
-		session->api().sensitiveContent().reload();
-	}, container->lifetime());
-	inner->add(object_ptr<Button>(
-		inner,
-		tr::lng_settings_sensitive_disable_filtering(),
-		st::settingsButtonNoIcon
-	))->toggleOn(
-		session->api().sensitiveContent().enabled()
-	)->toggledChanges(
-	) | rpl::filter([=](bool toggled) {
-		return toggled != session->api().sensitiveContent().enabledCurrent();
-	}) | rpl::start_with_next([=](bool toggled) {
-		session->api().sensitiveContent().update(toggled);
-	}, container->lifetime());
-
-	Ui::AddSkip(inner);
-	Ui::AddDividerText(inner, tr::lng_settings_sensitive_about());
-
-	wrap->toggleOn(session->api().sensitiveContent().canChange());
-}
-
 void SetupSelfDestruction(
 		not_null<Window::SessionController*> controller,
 		not_null<Ui::VerticalLayout*> container,
@@ -911,6 +870,47 @@ void SetupSecurity(
 
 } // namespace
 
+void SetupSensitiveContent(
+		not_null<Window::SessionController*> controller,
+		not_null<Ui::VerticalLayout*> container,
+		rpl::producer<> updateTrigger) {
+	using namespace rpl::mappers;
+
+	const auto wrap = container->add(
+		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
+			container,
+			object_ptr<Ui::VerticalLayout>(container)));
+	const auto inner = wrap->entity();
+
+	Ui::AddSkip(inner);
+	Ui::AddSubsectionTitle(inner, tr::lng_settings_sensitive_title());
+
+	const auto session = &controller->session();
+
+	std::move(
+		updateTrigger
+	) | rpl::start_with_next([=] {
+		session->api().sensitiveContent().reload();
+	}, container->lifetime());
+	inner->add(object_ptr<Button>(
+		inner,
+		tr::lng_settings_sensitive_disable_filtering(),
+		st::settingsButtonNoIcon
+	))->toggleOn(
+		session->api().sensitiveContent().enabled()
+	)->toggledChanges(
+	) | rpl::filter([=](bool toggled) {
+		return toggled != session->api().sensitiveContent().enabledCurrent();
+	}) | rpl::start_with_next([=](bool toggled) {
+		session->api().sensitiveContent().update(toggled);
+	}, container->lifetime());
+
+	Ui::AddSkip(inner);
+	Ui::AddDividerText(inner, tr::lng_settings_sensitive_about());
+
+	wrap->toggleOn(session->api().sensitiveContent().canChange());
+}
+
 int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions) {
 	const auto add = [](int already, not_null<PeerData*> peer) {
 		if (const auto chat = peer->asChat()) {
@@ -1099,11 +1099,6 @@ void PrivacySecurity::setupContent(
 	SetupSecurity(controller, content, trigger(), showOtherMethod());
 	SetupPrivacy(controller, content, trigger());
 	SetupTopPeers(controller, content);
-#if !defined OS_MAC_STORE && !defined OS_WIN_STORE
-	SetupSensitiveContent(controller, content, trigger());
-#else // !OS_MAC_STORE && !OS_WIN_STORE
-	AddDivider(content);
-#endif // !OS_MAC_STORE && !OS_WIN_STORE
 	SetupArchiveAndMute(controller, content);
 	SetupConfirmationExtensions(controller, content);
 	SetupBotsAndWebsites(controller, content);
diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.h b/Telegram/SourceFiles/settings/settings_privacy_security.h
index 01922b01e..21933c051 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_security.h
+++ b/Telegram/SourceFiles/settings/settings_privacy_security.h
@@ -18,6 +18,11 @@ class BoxContent;
 
 namespace Settings {
 
+void SetupSensitiveContent(
+	not_null<Window::SessionController*> controller,
+	not_null<Ui::VerticalLayout*> container,
+	rpl::producer<> updateTrigger);
+
 int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions);
 
 bool CheckEditCloudPassword(not_null<::Main::Session*> session);