diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3e4be3074f..898d13bd93 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -148,6 +148,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group."; "lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel."; "lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You are not a member of the chat where it was posted."; +"lng_error_noforwards_group" = "Sorry, forwarding is disabled from this group."; +"lng_error_noforwards_channel" = "Sorry, forwarding is disabled from this channel."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -1808,6 +1810,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_contact_title" = "Edit contact name"; "lng_edit_channel_title" = "Edit channel"; "lng_edit_sign_messages" = "Sign messages"; +"lng_edit_allow_forwards" = "Allow saving content"; "lng_edit_group" = "Edit group"; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a33c18a771..b8b6bfca91 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -504,6 +504,11 @@ void ApiWrap::sendMessageFail( scheduled.removeSending(item); Ui::show(Box(tr::lng_cant_do_this(tr::now))); } + } else if (error.type() == qstr("CHAT_FORWARDS_RESTRICTED")) { + Ui::ShowMultilineToast({ .text = { peer->isBroadcast() + ? tr::lng_error_noforwards_channel(tr::now) + : tr::lng_error_noforwards_group(tr::now) + }, .duration = kJoinErrorDuration }); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 807b31a545..e7c32c8ba2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -277,6 +277,7 @@ private: std::optional description; std::optional hiddenPreHistory; std::optional signatures; + std::optional forwards; std::optional linkedChat; }; @@ -296,6 +297,7 @@ private: void fillLinkedChatButton(); //void fillInviteLinkButton(); void fillSignaturesButton(); + void fillForwardsButton(); void fillHistoryVisibilityButton(); void fillManageSection(); void fillPendingRequestsButton(); @@ -312,6 +314,7 @@ private: bool validateDescription(Saving &to) const; bool validateHistoryVisibility(Saving &to) const; bool validateSignatures(Saving &to) const; + bool validateForwards(Saving &to) const; void save(); void saveUsername(); @@ -320,6 +323,7 @@ private: void saveDescription(); void saveHistoryVisibility(); void saveSignatures(); + void saveForwards(); void savePhoto(); void pushSaveStage(FnMut &&lambda); void continueSave(); @@ -341,6 +345,7 @@ private: std::optional _historyVisibilitySavedValue; std::optional _usernameSavedValue; std::optional _signaturesSavedValue; + std::optional _forwardsSavedValue; const not_null _navigation; const not_null _box; @@ -795,6 +800,21 @@ void Controller::fillSignaturesButton() { }, _controls.buttonsLayout->lifetime()); } +void Controller::fillForwardsButton() { + Expects(_controls.buttonsLayout != nullptr); + + AddButtonWithText( + _controls.buttonsLayout, + tr::lng_edit_allow_forwards(), + rpl::single(QString()), + [=] {} + )->toggleOn(rpl::single(_peer->allowsForwarding()) + )->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _forwardsSavedValue = toggled; + }, _controls.buttonsLayout->lifetime()); +} + void Controller::fillHistoryVisibilityButton() { Expects(_controls.buttonsLayout != nullptr); @@ -864,6 +884,9 @@ void Controller::fillManageSection() { ? (channel->canEditSignatures() && !channel->isMegagroup()) : false; }(); + const auto canEditForwards = [&] { + return isChannel ? channel->amCreator() : chat->amCreator(); + }(); const auto canEditPreHistoryHidden = [&] { return isChannel ? channel->canEditPreHistoryHidden() @@ -937,8 +960,12 @@ void Controller::fillManageSection() { if (canEditSignatures) { fillSignaturesButton(); } + if (canEditForwards) { + fillForwardsButton(); + } if (canEditPreHistoryHidden || canEditSignatures + || canEditForwards //|| canEditInviteLinks || canViewOrEditLinkedChat || canEditUsername) { @@ -1154,7 +1181,8 @@ std::optional Controller::validate() const { && validateTitle(result) && validateDescription(result) && validateHistoryVisibility(result) - && validateSignatures(result)) { + && validateSignatures(result) + && validateForwards(result)) { return result; } return {}; @@ -1229,6 +1257,14 @@ bool Controller::validateSignatures(Saving &to) const { return true; } +bool Controller::validateForwards(Saving &to) const { + if (!_forwardsSavedValue.has_value()) { + return true; + } + to.forwards = _forwardsSavedValue; + return true; +} + void Controller::save() { Expects(_wrap != nullptr); @@ -1243,6 +1279,7 @@ void Controller::save() { pushSaveStage([=] { saveDescription(); }); pushSaveStage([=] { saveHistoryVisibility(); }); pushSaveStage([=] { saveSignatures(); }); + pushSaveStage([=] { saveForwards(); }); pushSaveStage([=] { savePhoto(); }); continueSave(); } @@ -1499,6 +1536,26 @@ void Controller::saveSignatures() { }).send(); } +void Controller::saveForwards() { + if (!_savingData.forwards + || *_savingData.forwards == _peer->allowsForwarding()) { + return continueSave(); + } + _api.request(MTPmessages_ToggleNoForwards( + _peer->input, + MTP_bool(!*_savingData.forwards) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + continueSave(); + }).fail([=](const MTP::Error &error) { + if (error.type() == qstr("CHAT_NOT_MODIFIED")) { + continueSave(); + } else { + cancelSave(); + } + }).send(); +} + void Controller::savePhoto() { auto image = _controls.photo ? _controls.photo->takeResultImage() diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index e16fd4447c..5329183146 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -470,6 +470,10 @@ bool ChannelData::canWrite() const { && !amRestricted(Restriction::SendMessages))); } +bool ChannelData::allowsForwarding() const { + return !(flags() & Flag::NoForwards); +} + bool ChannelData::canViewMembers() const { return flags() & Flag::CanViewParticipants; } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 420746b268..94bf7e645a 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -50,6 +50,7 @@ enum class ChannelDataFlag { CanViewParticipants = (1 << 17), HasLink = (1 << 18), SlowmodeEnabled = (1 << 19), + NoForwards = (1 << 20), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -293,6 +294,7 @@ public: // Like in ChatData. [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 217cac924c..59f1525d35 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -28,7 +28,7 @@ ChatData::ChatData(not_null owner, PeerId id) , inputChat(MTP_long(peerToChat(id).bare)) { _flags.changes( ) | rpl::start_with_next([=](const Flags::Change &change) { - if (change.diff & ChatDataFlag::CallNotEmpty) { + if (change.diff & Flag::CallNotEmpty) { if (const auto history = this->owner().historyLoaded(this)) { history->updateChatListEntry(); } @@ -63,6 +63,10 @@ bool ChatData::canWrite() const { return amIn() && !amRestricted(Restriction::SendMessages); } +bool ChatData::allowsForwarding() const { + return !(flags() & Flag::NoForwards); +} + bool ChatData::canEditInformation() const { return amIn() && !amRestricted(Restriction::ChangeInfo); } @@ -74,7 +78,7 @@ bool ChatData::canEditPermissions() const { bool ChatData::canEditUsername() const { return amCreator() - && (flags() & ChatDataFlag::CanSetUsername); + && (flags() & Flag::CanSetUsername); } bool ChatData::canEditPreHistoryHidden() const { @@ -222,7 +226,7 @@ void ChatData::setGroupCall( scheduleDate); owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); - addFlags(ChatDataFlag::CallActive); + addFlags(Flag::CallActive); }); } @@ -236,7 +240,7 @@ void ChatData::clearGroupCall() { _call = nullptr; } session().changes().peerUpdated(this, UpdateFlag::GroupCall); - removeFlags(ChatDataFlag::CallActive | ChatDataFlag::CallNotEmpty); + removeFlags(Flag::CallActive | Flag::CallNotEmpty); } void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) { diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 97f022c82e..3f8f4ab058 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -18,6 +18,7 @@ enum class ChatDataFlag { CallActive = (1 << 5), CallNotEmpty = (1 << 6), CanSetUsername = (1 << 7), + NoForwards = (1 << 8), }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; using ChatDataFlags = base::flags; @@ -109,6 +110,7 @@ public: // Like in ChannelData. [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 74aec4c17a..19ea594040 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -848,6 +848,17 @@ bool PeerData::canWrite() const { return false; } +bool PeerData::allowsForwarding() const { + if (const auto user = asUser()) { + return true; + } else if (const auto channel = asChannel()) { + return channel->allowsForwarding(); + } else if (const auto chat = asChat()) { + return chat->allowsForwarding(); + } + return false; +} + Data::RestrictionCheckResult PeerData::amRestricted( ChatRestriction right) const { using Result = Data::RestrictionCheckResult; diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index dd9376214e..800ddf9d15 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -274,6 +274,7 @@ public: } [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] Data::RestrictionCheckResult amRestricted( ChatRestriction right) const; [[nodiscard]] bool amAnonymous() const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index d3d13dda5f..ed4ccbd47c 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -641,7 +641,8 @@ not_null Session::processChat(const MTPChat &data) { | Flag::Deactivated | Flag::Forbidden | Flag::CallActive - | Flag::CallNotEmpty; + | Flag::CallNotEmpty + | Flag::NoForwards; const auto flagsSet = (data.is_left() ? Flag::Left : Flag()) | (data.is_kicked() ? Flag::Kicked : Flag()) | (data.is_creator() ? Flag::Creator : Flag()) @@ -651,7 +652,8 @@ not_null Session::processChat(const MTPChat &data) { || (chat->groupCall() && chat->groupCall()->fullCount() > 0)) ? Flag::CallNotEmpty - : Flag()); + : Flag()) + | (data.is_noforwards() ? Flag::NoForwards : Flag()); chat->setFlags((chat->flags() & ~flagsMask) | flagsSet); chat->count = data.vparticipants_count().v; @@ -739,10 +741,8 @@ not_null Session::processChat(const MTPChat &data) { | Flag::CallActive | Flag::CallNotEmpty | Flag::Forbidden - | (!minimal - ? Flag::Left - | Flag::Creator - : Flag()); + | (!minimal ? (Flag::Left | Flag::Creator) : Flag()) + | Flag::NoForwards; const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag()) | (data.is_verified() ? Flag::Verified : Flag()) | (data.is_scam() ? Flag::Scam : Flag()) @@ -762,7 +762,8 @@ not_null Session::processChat(const MTPChat &data) { | (!minimal ? (data.is_left() ? Flag::Left : Flag()) | (data.is_creator() ? Flag::Creator : Flag()) - : Flag()); + : Flag()) + | (data.is_noforwards() ? Flag::NoForwards : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setName( diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 062222c003..ddde57fce6 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1038,7 +1038,9 @@ void HistoryMessage::applySentMessage( } bool HistoryMessage::allowsForward() const { - return isRegular() && (!_media || _media->allowsForward()); + return isRegular() + && history()->peer->allowsForwarding() + && (!_media || _media->allowsForward()); } bool HistoryMessage::allowsSendNow() const {