From 9ea495f59d3a12a25e3c8836f104cdfb3bc19b6e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 13 Aug 2024 13:51:11 +0200
Subject: [PATCH] Support group-like channels.

---
 Telegram/Resources/langs/lang.strings         |  3 ++
 Telegram/SourceFiles/api/api_sending.cpp      | 10 ++---
 Telegram/SourceFiles/apiwrap.cpp              |  2 +-
 .../boxes/peers/edit_peer_info_box.cpp        | 43 ++++++++++++++++---
 Telegram/SourceFiles/data/data_channel.cpp    | 12 +++++-
 Telegram/SourceFiles/data/data_peer.cpp       |  9 ++--
 Telegram/SourceFiles/data/data_types.h        | 15 ++++---
 .../history/history_inner_widget.cpp          |  4 +-
 Telegram/SourceFiles/history/history_item.cpp | 40 ++++++++++++++---
 Telegram/SourceFiles/history/history_item.h   |  3 ++
 .../SourceFiles/history/history_widget.cpp    |  8 ++++
 .../history/view/history_view_bottom_info.cpp | 13 ++++--
 .../history/view/history_view_element.cpp     |  2 +-
 .../history/view/history_view_message.cpp     | 16 +++++--
 .../history/view/history_view_message.h       |  3 +-
 .../main/session/send_as_peers.cpp            | 11 +++--
 .../SourceFiles/ui/chat/choose_send_as.cpp    |  4 +-
 17 files changed, 152 insertions(+), 46 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 65bdb834d..2b765f110 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -3584,6 +3584,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_edit_channel_title" = "Edit channel";
 "lng_edit_bot_title" = "Edit bot";
 "lng_edit_sign_messages" = "Sign messages";
+"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
+"lng_edit_sign_profiles" = "Show authors' profiles";
+"lng_edit_sign_profiles_about" = "Add names and photos of admins to the messages they post, linking to their profiles.";
 "lng_edit_group" = "Edit group";
 "lng_edit_channel_color" = "Change name color";
 "lng_edit_channel_level_min" = "Level 1+";
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index c05a42e95..fb096e626 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -41,14 +41,14 @@ void InnerFillMessagePostFlags(
 		const SendOptions &options,
 		not_null<PeerData*> peer,
 		MessageFlags &flags) {
-	const auto anonymousPost = peer->amAnonymous();
 	if (ShouldSendSilent(peer, options)) {
 		flags |= MessageFlag::Silent;
 	}
-	if (!anonymousPost || options.sendAs) {
+	if (!peer->amAnonymous()) {
 		flags |= MessageFlag::HasFromId;
-		return;
-	} else if (peer->asMegagroup()) {
+	}
+	const auto channel = peer->asBroadcast();
+	if (!channel) {
 		return;
 	}
 	flags |= MessageFlag::Post;
@@ -57,7 +57,7 @@ void InnerFillMessagePostFlags(
 		return;
 	}
 	flags |= MessageFlag::HasViews;
-	if (peer->asChannel()->addsSignature()) {
+	if (channel->addsSignature()) {
 		flags |= MessageFlag::HasPostAuthor;
 	}
 }
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 0f0fa264b..5220341a3 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -3282,9 +3282,9 @@ void ApiWrap::forwardMessages(
 	if (!action.options.scheduled && !action.options.shortcutId) {
 		histories.readInbox(history);
 	}
+	const auto sendAs = action.options.sendAs;
 	const auto anonymousPost = peer->amAnonymous();
 	const auto silentPost = ShouldSendSilent(peer, action.options);
-	const auto sendAs = action.options.sendAs;
 
 	using SendFlag = MTPmessages_ForwardMessages::Flag;
 	auto flags = MessageFlags();
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 86dd68802..4af6d5fbd 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -1057,19 +1057,50 @@ void Controller::fillSignaturesButton() {
 		return;
 	}
 
-	AddButtonWithText(
+	const auto signs = AddButtonWithText(
 		_controls.buttonsLayout,
 		tr::lng_edit_sign_messages(),
 		rpl::single(QString()),
 		[] {},
 		{ &st::menuIconSigned }
-	)->toggleOn(rpl::single(channel->addsSignature())
-	)->toggledValue(
+	)->toggleOn(rpl::single(channel->addsSignature()));
+
+	const auto profiles = _controls.buttonsLayout->add(
+		object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
+			_controls.buttonsLayout,
+			EditPeerInfoBox::CreateButton(
+				_controls.buttonsLayout,
+				tr::lng_edit_sign_profiles(),
+				rpl::single(QString()),
+				[] {},
+				st::manageGroupTopButtonWithText,
+				{ &st::menuIconSigned })));
+	profiles->toggleOn(signs->toggledValue());
+	profiles->finishAnimating();
+
+	profiles->entity()->toggleOn(rpl::single(
+		channel->addsSignature() && channel->signatureProfiles()
+	))->toggledValue(
+	) | rpl::start_with_next([=](bool toggled) {
+		_signatureProfilesSavedValue = toggled;
+	}, profiles->entity()->lifetime());
+
+	signs->toggledValue(
 	) | rpl::start_with_next([=](bool toggled) {
 		_signaturesSavedValue = toggled;
+		if (!toggled) {
+			_signatureProfilesSavedValue = false;
+		}
 	}, _controls.buttonsLayout->lifetime());
 
-	_signatureProfilesSavedValue = channel->signatureProfiles();
+	Ui::AddSkip(_controls.buttonsLayout);
+	Ui::AddDividerText(
+		_controls.buttonsLayout,
+		rpl::conditional(
+			signs->toggledValue(),
+			tr::lng_edit_sign_profiles_about(Ui::Text::WithEntities),
+			tr::lng_edit_sign_messages_about(Ui::Text::WithEntities)));
+	Ui::AddSkip(_controls.buttonsLayout);
 }
 
 void Controller::fillHistoryVisibilityButton() {
@@ -1227,11 +1258,9 @@ void Controller::fillManageSection() {
 	}
 	if (canEditSignatures) {
 		fillSignaturesButton();
-	}
-	if (canEditPreHistoryHidden
+	} else if (canEditPreHistoryHidden
 		|| canEditForum
 		|| canEditColorIndex
-		|| canEditSignatures
 		//|| canEditInviteLinks
 		|| canViewOrEditLinkedChat
 		|| canEditType) {
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 7c2846d5d..39e41a753 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -184,7 +184,11 @@ void ChannelData::setFlags(ChannelDataFlags which) {
 			});
 		}
 	}
-	if (diff & (Flag::Forum | Flag::CallNotEmpty | Flag::SimilarExpanded)) {
+	if (diff & (Flag::Forum
+		| Flag::CallNotEmpty
+		| Flag::SimilarExpanded
+		| Flag::Signatures
+		| Flag::SignatureProfiles)) {
 		if (const auto history = this->owner().historyLoaded(this)) {
 			if (diff & Flag::CallNotEmpty) {
 				history->updateChatListEntry();
@@ -203,6 +207,12 @@ void ChannelData::setFlags(ChannelDataFlags which) {
 					history->owner().requestItemResize(item);
 				}
 			}
+			if (diff & Flag::SignatureProfiles) {
+				history->forceFullResize();
+			}
+			if (diff & (Flag::Signatures | Flag::SignatureProfiles)) {
+				session().changes().peerUpdated(this, UpdateFlag::Rights);
+			}
 		}
 	}
 	if (const auto raw = taken.get()) {
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 68ef86684..08aaca699 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -1268,9 +1268,12 @@ Data::RestrictionCheckResult PeerData::amRestricted(
 }
 
 bool PeerData::amAnonymous() const {
-	return isBroadcast()
-		|| (isChannel()
-			&& (asChannel()->adminRights() & ChatAdminRight::Anonymous));
+	if (const auto channel = asChannel()) {
+		return channel->isBroadcast()
+			? !channel->signatureProfiles()
+			: (channel->adminRights() & ChatAdminRight::Anonymous);
+	}
+	return false;
 }
 
 bool PeerData::canRevokeFullHistory() const {
diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h
index 515a6556f..932844085 100644
--- a/Telegram/SourceFiles/data/data_types.h
+++ b/Telegram/SourceFiles/data/data_types.h
@@ -313,19 +313,20 @@ enum class MessageFlag : uint64 {
 
 	// If not set then we need to refresh _displayFrom value.
 	DisplayFromChecked    = (1ULL << 40),
+	DisplayFromProfiles   = (1ULL << 41),
 
-	ShowSimilarChannels   = (1ULL << 41),
+	ShowSimilarChannels   = (1ULL << 42),
 
-	Sponsored             = (1ULL << 42),
+	Sponsored             = (1ULL << 43),
 
-	ReactionsAreTags      = (1ULL << 43),
+	ReactionsAreTags      = (1ULL << 44),
 
-	ShortcutMessage       = (1ULL << 44),
+	ShortcutMessage       = (1ULL << 45),
 
-	EffectWatched         = (1ULL << 45),
+	EffectWatched         = (1ULL << 46),
 
-	SensitiveContent      = (1ULL << 46),
-	AllowSensitive        = (1ULL << 47),
+	SensitiveContent      = (1ULL << 47),
+	AllowSensitive        = (1ULL << 48),
 };
 inline constexpr bool is_flag_type(MessageFlag) { return true; }
 using MessageFlags = base::flags<MessageFlag>;
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index b5b36e80d..57409c365 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -730,8 +730,8 @@ bool HistoryInner::canHaveFromUserpics() const {
 		&& !_peer->isRepliesChat()
 		&& !_isChatWide) {
 		return false;
-	} else if (_peer->isChannel() && !_peer->isMegagroup()) {
-		return false;
+	} else if (const auto channel = _peer->asBroadcast()) {
+		return channel->signatureProfiles();
 	}
 	return true;
 }
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 4f0b5fa8a..18e803aeb 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -1232,10 +1232,19 @@ PeerData *HistoryItem::computeDisplayFrom() const {
 }
 
 PeerData *HistoryItem::displayFrom() const {
-	if (!(_flags & MessageFlag::DisplayFromChecked)) {
-		_flags |= MessageFlag::DisplayFromChecked;
-		_displayFrom = computeDisplayFrom();
+	if (_flags & MessageFlag::DisplayFromChecked) {
+		const auto showing = isPostShowingAuthor();
+		const auto flag = (_flags & MessageFlag::DisplayFromProfiles);
+		if (showing && !flag) {
+			_flags |= MessageFlag::DisplayFromProfiles;
+		} else if (!showing && flag) {
+			_flags &= ~MessageFlag::DisplayFromProfiles;
+		} else {
+			return _displayFrom;
+		}
 	}
+	_flags |= MessageFlag::DisplayFromChecked;
+	_displayFrom = computeDisplayFrom();
 	return _displayFrom;
 }
 
@@ -2148,7 +2157,7 @@ QString HistoryItem::notificationHeader() const {
 		return QString();
 	} else if (out() && isFromScheduled() && !_history->peer->isSelf()) {
 		return tr::lng_from_you(tr::now);
-	} else if (!_history->peer->isUser() && !isPost()) {
+	} else if (!_history->peer->isUser() && !isPostHidingAuthor()) {
 		return from()->name();
 	}
 	return QString();
@@ -2746,7 +2755,9 @@ bool HistoryItem::inThread(MsgId rootId) const {
 }
 
 not_null<PeerData*> HistoryItem::author() const {
-	return (isPost() && !isSponsored()) ? _history->peer : from();
+	return (isPostHidingAuthor() && !isSponsored())
+		? _history->peer
+		: from();
 }
 
 TimeId HistoryItem::originalDate() const {
@@ -3092,6 +3103,23 @@ bool HistoryItem::isUploading() const {
 	return _media && _media->uploading();
 }
 
+bool HistoryItem::hasRealFromId() const {
+	return !isPost() || (_flags & MessageFlag::HasFromId);
+}
+
+bool HistoryItem::isPostHidingAuthor() const {
+	if (!isPost()) {
+		return false;
+	} else if (const auto channel = _history->peer->asBroadcast()) {
+		return !channel->signatureProfiles();
+	}
+	return false; // Should not happen, I guess.
+}
+
+bool HistoryItem::isPostShowingAuthor() const {
+	return isPost() && !isPostHidingAuthor();
+}
+
 bool HistoryItem::isRegular() const {
 	return isHistoryEntry() && !isLocal();
 }
@@ -3464,7 +3492,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
 		return {};
 	};
 	const auto sender = [&]() -> std::optional<QString> {
-		if (options.hideSender || isPost() || isEmpty()) {
+		if (options.hideSender || isPostHidingAuthor() || isEmpty()) {
 			return {};
 		} else if (!_history->peer->isUser()) {
 			if (const auto from = displayFrom()) {
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index a11c6ce97..6b10b6534 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -327,6 +327,9 @@ public:
 	[[nodiscard]] bool showSimilarChannels() const {
 		return _flags & MessageFlag::ShowSimilarChannels;
 	}
+	[[nodiscard]] bool hasRealFromId() const;
+	[[nodiscard]] bool isPostHidingAuthor() const;
+	[[nodiscard]] bool isPostShowingAuthor() const;
 	[[nodiscard]] bool isRegular() const;
 	[[nodiscard]] bool isUploading() const;
 	void sendFailed();
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 86012f331..2b3696f35 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -822,6 +822,14 @@ HistoryWidget::HistoryWidget(
 			updateStickersByEmoji();
 			updateFieldPlaceholder();
 			_preview->checkNow(false);
+
+			const auto was = (_sendAs != nullptr);
+			refreshSendAsToggle();
+			if (was != (_sendAs != nullptr)) {
+				updateControlsVisibility();
+				updateControlsGeometry();
+				orderWidgets();
+			}
 		}
 		if (flags & PeerUpdateFlag::Migration) {
 			handlePeerMigration();
diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
index 42ea80dce..6784c7eca 100644
--- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/click_handler_types.h"
 #include "main/main_session.h"
 #include "lottie/lottie_icon.h"
+#include "data/data_channel.h"
 #include "data/data_session.h"
 #include "data/data_message_reactions.h"
 #include "window/window_session_controller.h"
@@ -570,10 +571,14 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
 	if (message->context() == Context::ShortcutMessages) {
 		result.flags |= Flag::Shortcut;
 	}
-	if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
-		 if (!msgsigned->isAnonymousRank) {
-			result.author = msgsigned->author;
-		 }
+	if (!item->isPost()
+		|| !item->hasRealFromId()
+		|| !item->history()->peer->asChannel()->signatureProfiles()) {
+		if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
+			if (!msgsigned->isAnonymousRank) {
+				result.author = msgsigned->author;
+			}
+		}
 	}
 	if (message->displayedEditDate()) {
 		result.flags |= Flag::Edited;
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index 50f5b7454..cb27621ba 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -1087,7 +1087,7 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
 		const auto item = view->data();
 		return !item->isService()
 			&& !item->isEmpty()
-			&& !item->isPost()
+			&& !item->isPostHidingAuthor()
 			&& (!item->history()->peer->isMegagroup()
 				|| !view->hasOutLayout()
 				|| !item->from()->isChannel());
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 50ee782d8..345ecf954 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -408,6 +408,7 @@ Message::Message(
 	Element *replacing)
 : Element(delegate, data, replacing, Flag(0))
 , _hideReply(delegate->elementHideReply(this))
+, _postShowingAuthor(data->isPostShowingAuthor() ? 1 : 0)
 , _bottomInfo(
 		&data->history()->owner().reactions(),
 		BottomInfoDataFromMessage(this)) {
@@ -2272,10 +2273,11 @@ bool Message::hasFromPhoto() const {
 	case Context::SavedSublist:
 	case Context::ScheduledTopic: {
 		const auto item = data();
-		if (item->isPost()) {
+		if (item->isPostHidingAuthor()) {
 			return false;
-		}
-		if (item->isEmpty()
+		} else if (item->isPost()) {
+			return true;
+		} else if (item->isEmpty()
 			|| (context() == Context::Replies && item->isDiscussionPost())) {
 			return false;
 		} else if (delegate()->elementIsChatWide()) {
@@ -4244,6 +4246,14 @@ int Message::resizeContentGetHeight(int newWidth) {
 	const auto mediaDisplayed = media ? media->isDisplayed() : false;
 	const auto bubble = drawBubble();
 
+	const auto postShowingAuthor = item->isPostShowingAuthor() ? 1 : 0;
+	if (_postShowingAuthor != postShowingAuthor) {
+		_postShowingAuthor = postShowingAuthor;
+		_bottomInfo.update(BottomInfoDataFromMessage(this), newWidth);
+		_fromNameVersion = -1;
+		previousInBlocksChanged();
+	}
+
 	item->resolveDependent();
 
 	// This code duplicates countGeometry() but also resizes media.
diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h
index 48a45bb03..6b8c28d48 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_message.h
@@ -315,10 +315,11 @@ private:
 	mutable std::unique_ptr<FromNameStatus> _fromNameStatus;
 	Ui::Text::String _rightBadge;
 	mutable int _fromNameVersion = 0;
-	uint32 _bubbleWidthLimit : 29 = 0;
+	uint32 _bubbleWidthLimit : 28 = 0;
 	uint32 _invertMedia : 1 = 0;
 	uint32 _hideReply : 1 = 0;
 	uint32 _rightBadgeHasBoosts : 1 = 0;
+	uint32 _postShowingAuthor : 1 = 0;
 
 	BottomInfo _bottomInfo;
 
diff --git a/Telegram/SourceFiles/main/session/send_as_peers.cpp b/Telegram/SourceFiles/main/session/send_as_peers.cpp
index 17065d7ef..ab75de103 100644
--- a/Telegram/SourceFiles/main/session/send_as_peers.cpp
+++ b/Telegram/SourceFiles/main/session/send_as_peers.cpp
@@ -43,11 +43,16 @@ SendAsPeers::SendAsPeers(not_null<Session*> session)
 
 bool SendAsPeers::shouldChoose(not_null<PeerData*> peer) {
 	refresh(peer);
-	return Data::CanSendAnything(peer, false) && (list(peer).size() > 1);
+	const auto channel = peer->asBroadcast();
+	return Data::CanSendAnything(peer, false)
+		&& (list(peer).size() > 1)
+		&& (!channel
+			|| channel->addsSignature()
+			|| channel->signatureProfiles());
 }
 
 void SendAsPeers::refresh(not_null<PeerData*> peer, bool force) {
-	if (!peer->isMegagroup()) {
+	if (!peer->isChannel()) {
 		return;
 	}
 	const auto now = crl::now();
@@ -117,7 +122,7 @@ not_null<PeerData*> SendAsPeers::ResolveChosen(
 		? i->peer
 		: !list.empty()
 		? list.front().peer
-		: (peer->isMegagroup() && peer->amAnonymous())
+		: peer->amAnonymous()
 		? peer
 		: peer->session().user();
 }
diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp
index 0856b044a..d7c7f08ab 100644
--- a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp
+++ b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp
@@ -263,9 +263,9 @@ void SetupSendAsButton(
 
 	auto userpic = current->value(
 	) | rpl::filter([=](PeerData *peer) {
-		return peer && peer->isMegagroup();
+		return peer && peer->isChannel();
 	}) | rpl::map([=](not_null<PeerData*> peer) {
-		const auto channel = peer->asMegagroup();
+		const auto channel = peer->asChannel();
 
 		auto updates = rpl::single(
 			rpl::empty