Allow restricting forwards in groups / channels.

This commit is contained in:
John Preston 2021-11-05 18:03:17 +04:00
parent 431e3035af
commit 9be47f0870
11 changed files with 105 additions and 13 deletions

View file

@ -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" = "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_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_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" = "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_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?"; "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_contact_title" = "Edit contact name";
"lng_edit_channel_title" = "Edit channel"; "lng_edit_channel_title" = "Edit channel";
"lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages" = "Sign messages";
"lng_edit_allow_forwards" = "Allow saving content";
"lng_edit_group" = "Edit group"; "lng_edit_group" = "Edit group";
"lng_edit_self_title" = "Edit your name"; "lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact"; "lng_confirm_contact_data" = "New Contact";

View file

@ -504,6 +504,11 @@ void ApiWrap::sendMessageFail(
scheduled.removeSending(item); scheduled.removeSending(item);
Ui::show(Box<Ui::InformBox>(tr::lng_cant_do_this(tr::now))); Ui::show(Box<Ui::InformBox>(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)) { if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0); Assert(randomId != 0);

View file

@ -277,6 +277,7 @@ private:
std::optional<QString> description; std::optional<QString> description;
std::optional<bool> hiddenPreHistory; std::optional<bool> hiddenPreHistory;
std::optional<bool> signatures; std::optional<bool> signatures;
std::optional<bool> forwards;
std::optional<ChannelData*> linkedChat; std::optional<ChannelData*> linkedChat;
}; };
@ -296,6 +297,7 @@ private:
void fillLinkedChatButton(); void fillLinkedChatButton();
//void fillInviteLinkButton(); //void fillInviteLinkButton();
void fillSignaturesButton(); void fillSignaturesButton();
void fillForwardsButton();
void fillHistoryVisibilityButton(); void fillHistoryVisibilityButton();
void fillManageSection(); void fillManageSection();
void fillPendingRequestsButton(); void fillPendingRequestsButton();
@ -312,6 +314,7 @@ private:
bool validateDescription(Saving &to) const; bool validateDescription(Saving &to) const;
bool validateHistoryVisibility(Saving &to) const; bool validateHistoryVisibility(Saving &to) const;
bool validateSignatures(Saving &to) const; bool validateSignatures(Saving &to) const;
bool validateForwards(Saving &to) const;
void save(); void save();
void saveUsername(); void saveUsername();
@ -320,6 +323,7 @@ private:
void saveDescription(); void saveDescription();
void saveHistoryVisibility(); void saveHistoryVisibility();
void saveSignatures(); void saveSignatures();
void saveForwards();
void savePhoto(); void savePhoto();
void pushSaveStage(FnMut<void()> &&lambda); void pushSaveStage(FnMut<void()> &&lambda);
void continueSave(); void continueSave();
@ -341,6 +345,7 @@ private:
std::optional<HistoryVisibility> _historyVisibilitySavedValue; std::optional<HistoryVisibility> _historyVisibilitySavedValue;
std::optional<QString> _usernameSavedValue; std::optional<QString> _usernameSavedValue;
std::optional<bool> _signaturesSavedValue; std::optional<bool> _signaturesSavedValue;
std::optional<bool> _forwardsSavedValue;
const not_null<Window::SessionNavigation*> _navigation; const not_null<Window::SessionNavigation*> _navigation;
const not_null<Ui::BoxContent*> _box; const not_null<Ui::BoxContent*> _box;
@ -795,6 +800,21 @@ void Controller::fillSignaturesButton() {
}, _controls.buttonsLayout->lifetime()); }, _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() { void Controller::fillHistoryVisibilityButton() {
Expects(_controls.buttonsLayout != nullptr); Expects(_controls.buttonsLayout != nullptr);
@ -864,6 +884,9 @@ void Controller::fillManageSection() {
? (channel->canEditSignatures() && !channel->isMegagroup()) ? (channel->canEditSignatures() && !channel->isMegagroup())
: false; : false;
}(); }();
const auto canEditForwards = [&] {
return isChannel ? channel->amCreator() : chat->amCreator();
}();
const auto canEditPreHistoryHidden = [&] { const auto canEditPreHistoryHidden = [&] {
return isChannel return isChannel
? channel->canEditPreHistoryHidden() ? channel->canEditPreHistoryHidden()
@ -937,8 +960,12 @@ void Controller::fillManageSection() {
if (canEditSignatures) { if (canEditSignatures) {
fillSignaturesButton(); fillSignaturesButton();
} }
if (canEditForwards) {
fillForwardsButton();
}
if (canEditPreHistoryHidden if (canEditPreHistoryHidden
|| canEditSignatures || canEditSignatures
|| canEditForwards
//|| canEditInviteLinks //|| canEditInviteLinks
|| canViewOrEditLinkedChat || canViewOrEditLinkedChat
|| canEditUsername) { || canEditUsername) {
@ -1154,7 +1181,8 @@ std::optional<Controller::Saving> Controller::validate() const {
&& validateTitle(result) && validateTitle(result)
&& validateDescription(result) && validateDescription(result)
&& validateHistoryVisibility(result) && validateHistoryVisibility(result)
&& validateSignatures(result)) { && validateSignatures(result)
&& validateForwards(result)) {
return result; return result;
} }
return {}; return {};
@ -1229,6 +1257,14 @@ bool Controller::validateSignatures(Saving &to) const {
return true; return true;
} }
bool Controller::validateForwards(Saving &to) const {
if (!_forwardsSavedValue.has_value()) {
return true;
}
to.forwards = _forwardsSavedValue;
return true;
}
void Controller::save() { void Controller::save() {
Expects(_wrap != nullptr); Expects(_wrap != nullptr);
@ -1243,6 +1279,7 @@ void Controller::save() {
pushSaveStage([=] { saveDescription(); }); pushSaveStage([=] { saveDescription(); });
pushSaveStage([=] { saveHistoryVisibility(); }); pushSaveStage([=] { saveHistoryVisibility(); });
pushSaveStage([=] { saveSignatures(); }); pushSaveStage([=] { saveSignatures(); });
pushSaveStage([=] { saveForwards(); });
pushSaveStage([=] { savePhoto(); }); pushSaveStage([=] { savePhoto(); });
continueSave(); continueSave();
} }
@ -1499,6 +1536,26 @@ void Controller::saveSignatures() {
}).send(); }).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() { void Controller::savePhoto() {
auto image = _controls.photo auto image = _controls.photo
? _controls.photo->takeResultImage() ? _controls.photo->takeResultImage()

View file

@ -470,6 +470,10 @@ bool ChannelData::canWrite() const {
&& !amRestricted(Restriction::SendMessages))); && !amRestricted(Restriction::SendMessages)));
} }
bool ChannelData::allowsForwarding() const {
return !(flags() & Flag::NoForwards);
}
bool ChannelData::canViewMembers() const { bool ChannelData::canViewMembers() const {
return flags() & Flag::CanViewParticipants; return flags() & Flag::CanViewParticipants;
} }

View file

@ -50,6 +50,7 @@ enum class ChannelDataFlag {
CanViewParticipants = (1 << 17), CanViewParticipants = (1 << 17),
HasLink = (1 << 18), HasLink = (1 << 18),
SlowmodeEnabled = (1 << 19), SlowmodeEnabled = (1 << 19),
NoForwards = (1 << 20),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;
@ -293,6 +294,7 @@ public:
// Like in ChatData. // Like in ChatData.
[[nodiscard]] bool canWrite() const; [[nodiscard]] bool canWrite() const;
[[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditInformation() const;
[[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditPermissions() const;
[[nodiscard]] bool canEditUsername() const; [[nodiscard]] bool canEditUsername() const;

View file

@ -28,7 +28,7 @@ ChatData::ChatData(not_null<Data::Session*> owner, PeerId id)
, inputChat(MTP_long(peerToChat(id).bare)) { , inputChat(MTP_long(peerToChat(id).bare)) {
_flags.changes( _flags.changes(
) | rpl::start_with_next([=](const Flags::Change &change) { ) | 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)) { if (const auto history = this->owner().historyLoaded(this)) {
history->updateChatListEntry(); history->updateChatListEntry();
} }
@ -63,6 +63,10 @@ bool ChatData::canWrite() const {
return amIn() && !amRestricted(Restriction::SendMessages); return amIn() && !amRestricted(Restriction::SendMessages);
} }
bool ChatData::allowsForwarding() const {
return !(flags() & Flag::NoForwards);
}
bool ChatData::canEditInformation() const { bool ChatData::canEditInformation() const {
return amIn() && !amRestricted(Restriction::ChangeInfo); return amIn() && !amRestricted(Restriction::ChangeInfo);
} }
@ -74,7 +78,7 @@ bool ChatData::canEditPermissions() const {
bool ChatData::canEditUsername() const { bool ChatData::canEditUsername() const {
return amCreator() return amCreator()
&& (flags() & ChatDataFlag::CanSetUsername); && (flags() & Flag::CanSetUsername);
} }
bool ChatData::canEditPreHistoryHidden() const { bool ChatData::canEditPreHistoryHidden() const {
@ -222,7 +226,7 @@ void ChatData::setGroupCall(
scheduleDate); scheduleDate);
owner().registerGroupCall(_call.get()); owner().registerGroupCall(_call.get());
session().changes().peerUpdated(this, UpdateFlag::GroupCall); session().changes().peerUpdated(this, UpdateFlag::GroupCall);
addFlags(ChatDataFlag::CallActive); addFlags(Flag::CallActive);
}); });
} }
@ -236,7 +240,7 @@ void ChatData::clearGroupCall() {
_call = nullptr; _call = nullptr;
} }
session().changes().peerUpdated(this, UpdateFlag::GroupCall); session().changes().peerUpdated(this, UpdateFlag::GroupCall);
removeFlags(ChatDataFlag::CallActive | ChatDataFlag::CallNotEmpty); removeFlags(Flag::CallActive | Flag::CallNotEmpty);
} }
void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) { void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) {

View file

@ -18,6 +18,7 @@ enum class ChatDataFlag {
CallActive = (1 << 5), CallActive = (1 << 5),
CallNotEmpty = (1 << 6), CallNotEmpty = (1 << 6),
CanSetUsername = (1 << 7), CanSetUsername = (1 << 7),
NoForwards = (1 << 8),
}; };
inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; };
using ChatDataFlags = base::flags<ChatDataFlag>; using ChatDataFlags = base::flags<ChatDataFlag>;
@ -109,6 +110,7 @@ public:
// Like in ChannelData. // Like in ChannelData.
[[nodiscard]] bool canWrite() const; [[nodiscard]] bool canWrite() const;
[[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditInformation() const;
[[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditPermissions() const;
[[nodiscard]] bool canEditUsername() const; [[nodiscard]] bool canEditUsername() const;

View file

@ -848,6 +848,17 @@ bool PeerData::canWrite() const {
return false; 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( Data::RestrictionCheckResult PeerData::amRestricted(
ChatRestriction right) const { ChatRestriction right) const {
using Result = Data::RestrictionCheckResult; using Result = Data::RestrictionCheckResult;

View file

@ -274,6 +274,7 @@ public:
} }
[[nodiscard]] bool canWrite() const; [[nodiscard]] bool canWrite() const;
[[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] Data::RestrictionCheckResult amRestricted( [[nodiscard]] Data::RestrictionCheckResult amRestricted(
ChatRestriction right) const; ChatRestriction right) const;
[[nodiscard]] bool amAnonymous() const; [[nodiscard]] bool amAnonymous() const;

View file

@ -641,7 +641,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
| Flag::Deactivated | Flag::Deactivated
| Flag::Forbidden | Flag::Forbidden
| Flag::CallActive | Flag::CallActive
| Flag::CallNotEmpty; | Flag::CallNotEmpty
| Flag::NoForwards;
const auto flagsSet = (data.is_left() ? Flag::Left : Flag()) const auto flagsSet = (data.is_left() ? Flag::Left : Flag())
| (data.is_kicked() ? Flag::Kicked : Flag()) | (data.is_kicked() ? Flag::Kicked : Flag())
| (data.is_creator() ? Flag::Creator : Flag()) | (data.is_creator() ? Flag::Creator : Flag())
@ -651,7 +652,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|| (chat->groupCall() || (chat->groupCall()
&& chat->groupCall()->fullCount() > 0)) && chat->groupCall()->fullCount() > 0))
? Flag::CallNotEmpty ? Flag::CallNotEmpty
: Flag()); : Flag())
| (data.is_noforwards() ? Flag::NoForwards : Flag());
chat->setFlags((chat->flags() & ~flagsMask) | flagsSet); chat->setFlags((chat->flags() & ~flagsMask) | flagsSet);
chat->count = data.vparticipants_count().v; chat->count = data.vparticipants_count().v;
@ -739,10 +741,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
| Flag::CallActive | Flag::CallActive
| Flag::CallNotEmpty | Flag::CallNotEmpty
| Flag::Forbidden | Flag::Forbidden
| (!minimal | (!minimal ? (Flag::Left | Flag::Creator) : Flag())
? Flag::Left | Flag::NoForwards;
| Flag::Creator
: Flag());
const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag()) const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
| (data.is_verified() ? Flag::Verified : Flag()) | (data.is_verified() ? Flag::Verified : Flag())
| (data.is_scam() ? Flag::Scam : Flag()) | (data.is_scam() ? Flag::Scam : Flag())
@ -762,7 +762,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
| (!minimal | (!minimal
? (data.is_left() ? Flag::Left : Flag()) ? (data.is_left() ? Flag::Left : Flag())
| (data.is_creator() ? Flag::Creator : Flag()) | (data.is_creator() ? Flag::Creator : Flag())
: Flag()); : Flag())
| (data.is_noforwards() ? Flag::NoForwards : Flag());
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
channel->setName( channel->setName(

View file

@ -1038,7 +1038,9 @@ void HistoryMessage::applySentMessage(
} }
bool HistoryMessage::allowsForward() const { bool HistoryMessage::allowsForward() const {
return isRegular() && (!_media || _media->allowsForward()); return isRegular()
&& history()->peer->allowsForwarding()
&& (!_media || _media->allowsForward());
} }
bool HistoryMessage::allowsSendNow() const { bool HistoryMessage::allowsSendNow() const {