Implement private chat noforward following.

This commit is contained in:
John Preston
2026-02-06 14:18:30 +04:00
parent 5c7d2723ed
commit 4e9c0ea3a5
12 changed files with 120 additions and 36 deletions
+3
View File
@@ -313,6 +313,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins.";
"lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins.";
"lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins.";
"lng_error_noforwards_user" = "Sorry, forwarding from this chat is restricted.";
"lng_error_nocopy_user" = "Sorry, copying from this chat is restricted.";
"lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying.";
"lng_error_schedule_limit" = "Sorry, you can't schedule more than 100 messages.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
@@ -5001,6 +5003,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_noforwards_info_channel" = "Copying and forwarding is not allowed in this channel.";
"lng_context_noforwards_info_group" = "Copying and forwarding is not allowed in this group.";
"lng_context_noforwards_info_bot" = "Copying and forwarding is not allowed from this bot.";
"lng_context_noforwards_info_user" = "Copying and forwarding is not allowed in this chat.";
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
+12
View File
@@ -2685,6 +2685,18 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto &data = update.c_updateEmojiGameInfo();
_session->diceStickersPacks().apply(data);
} break;
case mtpc_updatePeerHistoryNoForwards: {
const auto &d = update.c_updatePeerHistoryNoForwards();
const auto peerId = peerFromMTP(d.vpeer());
if (const auto peer = session().data().peerLoaded(peerId)) {
if (const auto user = peer->asUser()) {
user->setNoForwardsFlags(
d.is_my_enabled(),
d.is_peer_enabled());
}
}
} break;
}
}
+2
View File
@@ -524,6 +524,8 @@ void ApiWrap::sendMessageFail(
} else if (show && error == u"CHAT_FORWARDS_RESTRICTED"_q) {
show->showToast(peer->isBroadcast()
? tr::lng_error_noforwards_channel(tr::now)
: peer->isUser()
? tr::lng_error_noforwards_user(tr::now)
: tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration);
} else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
Settings::ShowPremium(&session(), "premium_stickers");
+2 -2
View File
@@ -1713,8 +1713,8 @@ void PeerData::processTopics(const MTPVector<MTPForumTopic> &topics) {
}
bool PeerData::allowsForwarding() const {
if (isUser()) {
return true;
if (const auto user = asUser()) {
return user->allowsForwarding();
} else if (const auto channel = asChannel()) {
return channel->allowsForwarding();
} else if (const auto chat = asChat()) {
+16
View File
@@ -646,6 +646,18 @@ bool UserData::readDatesPrivate() const {
return (flags() & UserDataFlag::ReadDatesPrivate);
}
bool UserData::allowsForwarding() const {
return !(flags() & Flag::NoForwardsMyEnabled)
&& !(flags() & Flag::NoForwardsPeerEnabled);
}
void UserData::setNoForwardsFlags(bool myEnabled, bool peerEnabled) {
const auto mask = Flag::NoForwardsMyEnabled | Flag::NoForwardsPeerEnabled;
setFlags((flags() & ~mask)
| (myEnabled ? Flag::NoForwardsMyEnabled : Flag())
| (peerEnabled ? Flag::NoForwardsPeerEnabled : Flag()));
}
int UserData::starsPerMessage() const {
return _starsPerMessage;
}
@@ -1036,6 +1048,10 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setNote(TextWithEntities());
}
user->setNoForwardsFlags(
update.is_noforwards_my_enabled(),
update.is_noforwards_peer_enabled());
user->fullUpdated();
}
+4
View File
@@ -135,6 +135,8 @@ enum class UserDataFlag : uint32 {
StoriesCorrespondent = (1 << 26),
Forum = (1 << 27),
HasActiveVideoStream = (1 << 28),
NoForwardsMyEnabled = (1 << 29),
NoForwardsPeerEnabled = (1 << 30),
};
inline constexpr bool is_flag_type(UserDataFlag) { return true; };
using UserDataFlags = base::flags<UserDataFlag>;
@@ -201,6 +203,8 @@ public:
[[nodiscard]] bool messageMoneyRestrictionsKnown() const;
[[nodiscard]] bool canSendIgnoreMoneyRestrictions() const;
[[nodiscard]] bool readDatesPrivate() const;
[[nodiscard]] bool allowsForwarding() const;
void setNoForwardsFlags(bool myEnabled, bool peerEnabled);
[[nodiscard]] bool isForum() const {
return flags() & Flag::Forum;
}
@@ -504,40 +504,65 @@ Main::Session &HistoryInner::session() const {
void HistoryInner::setupSharingDisallowed() {
Expects(_peer != nullptr);
if (_peer->isUser()) {
_sharingDisallowed = false;
return;
if (const auto user = _peer->asUser()) {
_sharingDisallowed = rpl::combine(
Data::PeerFlagValue(user, UserDataFlag::NoForwardsMyEnabled),
Data::PeerFlagValue(user, UserDataFlag::NoForwardsPeerEnabled)
) | rpl::map([](bool my, bool peer) {
return my || peer;
});
} else {
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
_sharingDisallowed = chat
? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards)
: Data::PeerFlagValue(
channel,
ChannelDataFlag::NoForwards
) | rpl::type_erased;
}
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
_sharingDisallowed = chat
? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards)
: Data::PeerFlagValue(
channel,
ChannelDataFlag::NoForwards
) | rpl::type_erased;
auto rights = chat
? chat->adminRightsValue()
: channel->adminRightsValue();
auto canDelete = std::move(
rights
) | rpl::map([=] {
return chat
? chat->canDeleteMessages()
: channel->canDeleteMessages();
});
rpl::combine(
_sharingDisallowed.value(),
std::move(canDelete)
) | rpl::filter([=](bool disallowed, bool canDelete) {
return hasSelectRestriction() && !getSelectedItems().empty();
}) | rpl::on_next([=] {
_widget->clearSelected();
if (_mouseAction == MouseAction::PrepareSelect) {
mouseActionCancel();
const auto clearIfRestricted = [=] {
if (hasSelectRestriction() && !getSelectedItems().empty()) {
_widget->clearSelected();
if (_mouseAction == MouseAction::PrepareSelect) {
mouseActionCancel();
}
}
}, lifetime());
};
if (const auto chat = _peer->asChat()) {
auto rights = chat->adminRightsValue();
auto canDelete = std::move(
rights
) | rpl::map([=] {
return chat->canDeleteMessages();
});
rpl::combine(
_sharingDisallowed.value(),
std::move(canDelete)
) | rpl::on_next([=] {
clearIfRestricted();
}, lifetime());
} else if (const auto channel = _peer->asChannel()) {
auto rights = channel->adminRightsValue();
auto canDelete = std::move(
rights
) | rpl::map([=] {
return channel->canDeleteMessages();
});
rpl::combine(
_sharingDisallowed.value(),
std::move(canDelete)
) | rpl::on_next([=] {
clearIfRestricted();
}, lifetime());
} else {
_sharingDisallowed.value(
) | rpl::on_next([=] {
clearIfRestricted();
}, lifetime());
}
}
void HistoryInner::setupSwipeReplyAndBack() {
@@ -3322,6 +3347,8 @@ bool HistoryInner::showCopyRestriction(HistoryItem *item) {
}
_controller->showToast(_peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: _peer->isUser()
? tr::lng_error_nocopy_user(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return true;
}
@@ -3332,6 +3359,8 @@ bool HistoryInner::showCopyMediaRestriction(not_null<HistoryItem*> item) {
}
_controller->showToast(_peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: _peer->isUser()
? tr::lng_error_nocopy_user(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return true;
}
@@ -2061,6 +2061,8 @@ void AddSelectRestrictionAction(
? tr::lng_context_noforwards_info_channel
: (peer->isUser() && peer->asUser()->isBot())
? tr::lng_context_noforwards_info_bot
: peer->isUser()
? tr::lng_context_noforwards_info_user
: tr::lng_context_noforwards_info_channel)(
tr::now,
tr::rich),
@@ -1617,6 +1617,8 @@ bool ListWidget::showCopyRestriction(HistoryItem *item) {
}
_delegate->listUiShow()->showToast((type == CopyRestrictionType::Channel)
? tr::lng_error_nocopy_channel(tr::now)
: (type == CopyRestrictionType::User)
? tr::lng_error_nocopy_user(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return true;
}
@@ -1628,6 +1630,8 @@ bool ListWidget::showCopyMediaRestriction(not_null<HistoryItem*> item) {
}
_delegate->listUiShow()->showToast((type == CopyRestrictionType::Channel)
? tr::lng_error_nocopy_channel(tr::now)
: (type == CopyRestrictionType::User)
? tr::lng_error_nocopy_user(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return true;
}
@@ -4508,6 +4512,8 @@ CopyRestrictionType CopyRestrictionTypeFor(
HistoryItem *item) {
return (peer->allowsForwarding() && (!item || !item->forbidsForward()))
? CopyRestrictionType::None
: peer->isUser()
? CopyRestrictionType::User
: peer->isBroadcast()
? CopyRestrictionType::Channel
: CopyRestrictionType::Group;
@@ -4522,6 +4528,8 @@ CopyRestrictionType CopyMediaRestrictionTypeFor(
}
return !item->forbidsSaving()
? CopyRestrictionType::None
: peer->isUser()
? CopyRestrictionType::User
: peer->isBroadcast()
? CopyRestrictionType::Channel
: CopyRestrictionType::Group;
@@ -4538,7 +4546,7 @@ CopyRestrictionType SelectRestrictionTypeFor(
? CopyRestrictionType::None
: CopyRestrictionTypeFor(peer);
}
return CopyRestrictionType::None;
return CopyRestrictionTypeFor(peer);
}
} // namespace HistoryView
@@ -71,6 +71,7 @@ enum class CopyRestrictionType : char {
None,
Group,
Channel,
User,
};
struct SelectedItem {
@@ -91,8 +91,13 @@ bool Provider::hasSelectRestriction() {
}
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
if (_peer->isUser()) {
return rpl::never<bool>();
if (const auto user = _peer->asUser()) {
return rpl::combine(
Data::PeerFlagValue(user, UserDataFlag::NoForwardsMyEnabled),
Data::PeerFlagValue(user, UserDataFlag::NoForwardsPeerEnabled)
) | rpl::map([=] {
return hasSelectRestriction();
}) | rpl::distinct_until_changed() | rpl::skip(1);
}
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
@@ -1164,6 +1164,8 @@ bool OverlayWidget::showCopyMediaRestriction(bool skipPRemiumCheck) {
} else if (_history) {
uiShow()->showToast(_history->peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: _history->peer->isUser()
? tr::lng_error_nocopy_user(tr::now)
: tr::lng_error_nocopy_group(tr::now));
}
return true;