diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a14ec5350e..08819cd5b7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 2454724110..636b81b161 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -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; } } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index c4c97630e6..228e726e25 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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"); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 2228505b24..8243d72cc0 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1713,8 +1713,8 @@ void PeerData::processTopics(const MTPVector &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()) { diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 700c14d416..d415246ead 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -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 user, const MTPDuserFull &update) { user->setNote(TextWithEntities()); } + user->setNoForwardsFlags( + update.is_noforwards_my_enabled(), + update.is_noforwards_peer_enabled()); + user->fullUpdated(); } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 4243548d93..4a8d21bcfd 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -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; @@ -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; } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6a6c584ba4..5169bb7bce 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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 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; } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 6967d2b66c..4034082710 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -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), diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index eeb3667bd4..701a8c32a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -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 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 diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index a0d0bec38f..cbb6a260ba 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -71,6 +71,7 @@ enum class CopyRestrictionType : char { None, Group, Channel, + User, }; struct SelectedItem { diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index 3b4680bad4..5420d9d6c5 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -91,8 +91,13 @@ bool Provider::hasSelectRestriction() { } rpl::producer Provider::hasSelectRestrictionChanges() { - if (_peer->isUser()) { - return rpl::never(); + 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(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 9b7a202b12..d63161b487 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -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;