Support separate message type group restrictions.

This commit is contained in:
John Preston 2023-01-10 22:56:20 +04:00
parent de5bbf2cb9
commit 554f66f089
64 changed files with 1437 additions and 832 deletions

View file

@ -3015,6 +3015,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_chat_send_links" = "Embed links"; "lng_rights_chat_send_links" = "Embed links";
"lng_rights_chat_send_polls" = "Send polls"; "lng_rights_chat_send_polls" = "Send polls";
"lng_rights_chat_add_members" = "Add members"; "lng_rights_chat_add_members" = "Add members";
"lng_rights_chat_photos" = "Photos";
"lng_rights_chat_videos" = "Video files";
"lng_rights_chat_stickers" = "Stickers & GIFs";
"lng_rights_chat_music" = "Music";
"lng_rights_chat_files" = "Files";
"lng_rights_chat_voice_messages" = "Voice messages";
"lng_rights_chat_video_messages" = "Video messages";
"lng_rights_chat_banned_until_header" = "Restricted until"; "lng_rights_chat_banned_until_header" = "Restricted until";
"lng_rights_chat_banned_forever" = "Forever"; "lng_rights_chat_banned_forever" = "Forever";
"lng_rights_chat_banned_day#one" = "For {count} day"; "lng_rights_chat_banned_day#one" = "For {count} day";
@ -3044,21 +3051,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bots_password_confirm_description" = "Please enter your password to confirm the action."; "lng_bots_password_confirm_description" = "Please enter your password to confirm the action.";
"lng_restricted_send_message" = "The admins of this group restricted you from writing here."; "lng_restricted_send_message" = "The admins of this group restricted you from writing here.";
"lng_restricted_send_media" = "The admins of this group restricted you from posting media content here."; "lng_restricted_send_photos" = "The admins of this group restricted you from posting photos here.";
"lng_restricted_send_videos" = "The admins of this group restricted you from posting video files here.";
"lng_restricted_send_music" = "The admins of this group restricted you from posting music here.";
"lng_restricted_send_files" = "The admins of this group restricted you from posting files here.";
"lng_restricted_send_voice_messages_group" = "The admins of this group restricted you from posting voice messages here.";
"lng_restricted_send_video_messages_group" = "The admins of this group restricted you from posting video messages here.";
"lng_restricted_send_stickers" = "The admins of this group restricted you from posting stickers here."; "lng_restricted_send_stickers" = "The admins of this group restricted you from posting stickers here.";
"lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here.";
"lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here.";
"lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here."; "lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here.";
"lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}."; "lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}.";
"lng_restricted_send_media_until" = "The admins of this group restricted you from posting media content here until {date}, {time}."; "lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}.";
"lng_restricted_send_videos_until" = "The admins of this group restricted you from posting video files here until {date}, {time}.";
"lng_restricted_send_music_until" = "The admins of this group restricted you from posting music here until {date}, {time}.";
"lng_restricted_send_files_until" = "The admins of this group restricted you from posting files here until {date}, {time}.";
"lng_restricted_send_voice_messages_until" = "The admins of this group restricted you from posting voice messages here until {date}, {time}.";
"lng_restricted_send_video_messages_until" = "The admins of this group restricted you from posting video messages here until {date}, {time}.";
"lng_restricted_send_stickers_until" = "The admins of this group restricted you from posting stickers here until {date}, {time}."; "lng_restricted_send_stickers_until" = "The admins of this group restricted you from posting stickers here until {date}, {time}.";
"lng_restricted_send_gifs_until" = "The admins of this group restricted you from posting GIFs here until {date}, {time}."; "lng_restricted_send_gifs_until" = "The admins of this group restricted you from posting GIFs here until {date}, {time}.";
"lng_restricted_send_inline_until" = "The admins of this group restricted you from posting inline content here until {date}, {time}."; "lng_restricted_send_inline_until" = "The admins of this group restricted you from posting inline content here until {date}, {time}.";
"lng_restricted_send_polls_until" = "The admins of this group restricted you from posting polls here until {date}, {time}."; "lng_restricted_send_polls_until" = "The admins of this group restricted you from posting polls here until {date}, {time}.";
"lng_restricted_send_message_all" = "Writing messages isn't allowed in this group."; "lng_restricted_send_message_all" = "Writing messages isn't allowed in this group.";
"lng_restricted_send_media_all" = "Posting media content isn't allowed in this group."; "lng_restricted_send_photos_all" = "Posting photos isn't allowed in this group.";
"lng_restricted_send_videos_all" = "Posting video files isn't allowed in this group.";
"lng_restricted_send_music_all" = "Posting music isn't allowed in this group.";
"lng_restricted_send_files_all" = "Posting files isn't allowed in this group.";
"lng_restricted_send_voice_messages_all" = "Posting voice messages isn't allowed in this group.";
"lng_restricted_send_video_messages_all" = "Posting video messages isn't allowed in this group.";
"lng_restricted_send_stickers_all" = "Posting stickers isn't allowed in this group."; "lng_restricted_send_stickers_all" = "Posting stickers isn't allowed in this group.";
"lng_restricted_send_gifs_all" = "Posting GIFs isn't allowed in this group."; "lng_restricted_send_gifs_all" = "Posting GIFs isn't allowed in this group.";
"lng_restricted_send_inline_all" = "Posting inline content isn't allowed in this group."; "lng_restricted_send_inline_all" = "Posting inline content isn't allowed in this group.";
@ -3219,7 +3241,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_restricted_until" = "until {date}"; "lng_admin_log_restricted_until" = "until {date}";
"lng_admin_log_banned_view_messages" = "Read messages"; "lng_admin_log_banned_view_messages" = "Read messages";
"lng_admin_log_banned_send_messages" = "Send messages"; "lng_admin_log_banned_send_messages" = "Send messages";
"lng_admin_log_banned_send_media" = "Send media"; "lng_admin_log_banned_send_photos" = "Send photos";
"lng_admin_log_banned_send_videos" = "Send video files";
"lng_admin_log_banned_send_music" = "Send music";
"lng_admin_log_banned_send_files" = "Send files";
"lng_admin_log_banned_send_voice_messages" = "Send voice messages";
"lng_admin_log_banned_send_video_messages" = "Send video messages";
"lng_admin_log_banned_send_stickers" = "Send stickers & GIFs"; "lng_admin_log_banned_send_stickers" = "Send stickers & GIFs";
"lng_admin_log_banned_embed_links" = "Embed links"; "lng_admin_log_banned_embed_links" = "Embed links";
"lng_admin_log_banned_send_polls" = "Send polls"; "lng_admin_log_banned_send_polls" = "Send polls";

View file

@ -3509,7 +3509,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
? action.topicRootId ? action.topicRootId
: Data::ForumTopic::kGeneralId; : Data::ForumTopic::kGeneralId;
const auto topic = peer->forumTopicFor(topicRootId); const auto topic = peer->forumTopicFor(topicRootId);
if (!(topic ? topic->canWrite() : peer->canWrite()) if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|| Api::SendDice(message)) { || Api::SendDice(message)) {
return; return;
} }

View file

@ -534,9 +534,8 @@ auto ChooseRecipientBoxController::createRow(
const auto peer = history->peer; const auto peer = history->peer;
const auto skip = _filter const auto skip = _filter
? !_filter(history) ? !_filter(history)
: ((peer->isBroadcast() && !peer->canWrite()) : ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| (peer->isUser() && !peer->canWrite()) || (peer->isUser() && !Data::CanSendAnything(peer)));
|| peer->isRepliesChat());
return skip ? nullptr : std::make_unique<Row>(history); return skip ? nullptr : std::make_unique<Row>(history);
} }
@ -752,6 +751,6 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic) auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
-> std::unique_ptr<Row> { -> std::unique_ptr<Row> {
const auto skip = _filter ? !_filter(topic) : !topic->canWrite(); const auto skip = _filter && !_filter(topic);
return skip ? nullptr : std::make_unique<Row>(topic); return skip ? nullptr : std::make_unique<Row>(topic);
}; };

View file

@ -1200,11 +1200,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
(*box)->closeBox(); (*box)->closeBox();
} }
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) {
return Data::CanSendTexts(thread);
};
auto object = Box<ShareBox>(ShareBox::Descriptor{ auto object = Box<ShareBox>(ShareBox::Descriptor{
.session = &peer->session(), .session = &peer->session(),
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = [](auto thread) { return thread->canWrite(); }, .filterCallback = std::move(filterCallback),
}); });
*box = Ui::MakeWeak(object.data()); *box = Ui::MakeWeak(object.data());
return object; return object;

View file

@ -123,23 +123,26 @@ auto Dependencies(ChatRestrictions)
{ Flag::SendInline, Flag::SendStickers }, { Flag::SendInline, Flag::SendStickers },
{ Flag::SendStickers, Flag::SendInline }, { Flag::SendStickers, Flag::SendInline },
// stickers -> send_messages // embed_links -> send_plain
{ Flag::SendStickers, Flag::SendMessages }, { Flag::EmbedLinks, Flag::SendOther },
// embed_links -> send_messages // send_* -> view_messages
{ Flag::EmbedLinks, Flag::SendMessages }, { Flag::SendStickers, Flag::ViewMessages },
{ Flag::SendGifs, Flag::ViewMessages },
// send_media -> send_messages { Flag::SendGames, Flag::ViewMessages },
{ Flag::SendMedia, Flag::SendMessages }, { Flag::SendInline, Flag::ViewMessages },
{ Flag::SendPolls, Flag::ViewMessages },
// send_polls -> send_messages { Flag::SendPhotos, Flag::ViewMessages },
{ Flag::SendPolls, Flag::SendMessages }, { Flag::SendVideos, Flag::ViewMessages },
{ Flag::SendVideoMessages, Flag::ViewMessages },
// send_messages -> view_messages { Flag::SendMusic, Flag::ViewMessages },
{ Flag::SendMessages, Flag::ViewMessages }, { Flag::SendVoiceMessages, Flag::ViewMessages },
{ Flag::SendFiles, Flag::ViewMessages },
{ Flag::SendOther, Flag::ViewMessages },
}; };
} }
ChatRestrictions NegateRestrictions(ChatRestrictions value) { ChatRestrictions NegateRestrictions(ChatRestrictions value) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
@ -154,10 +157,15 @@ ChatRestrictions NegateRestrictions(ChatRestrictions value) {
| Flag::SendGames | Flag::SendGames
| Flag::SendGifs | Flag::SendGifs
| Flag::SendInline | Flag::SendInline
| Flag::SendMedia
| Flag::SendMessages
| Flag::SendPolls | Flag::SendPolls
| Flag::SendStickers); | Flag::SendStickers
| Flag::SendPhotos
| Flag::SendVideos
| Flag::SendVideoMessages
| Flag::SendMusic
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther);
} }
auto Dependencies(ChatAdminRights) auto Dependencies(ChatAdminRights)
@ -722,13 +730,20 @@ void EditPeerPermissionsBox::addBannedButtons(
std::vector<RestrictionLabel> RestrictionLabels( std::vector<RestrictionLabel> RestrictionLabels(
Data::RestrictionsSetOptions options) { Data::RestrictionsSetOptions options) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
auto result = std::vector<RestrictionLabel>{ auto result = std::vector<RestrictionLabel>{
{ Flag::SendMessages, tr::lng_rights_chat_send_text(tr::now) }, { Flag::SendOther, tr::lng_rights_chat_send_text(tr::now) },
{ Flag::SendMedia, tr::lng_rights_chat_send_media(tr::now) }, // { Flag::SendMedia, tr::lng_rights_chat_send_media(tr::now) },
{ Flag::SendPhotos, tr::lng_rights_chat_photos(tr::now) },
{ Flag::SendVideos, tr::lng_rights_chat_videos(tr::now) },
{ Flag::SendVideoMessages, tr::lng_rights_chat_video_messages(tr::now) },
{ Flag::SendMusic, tr::lng_rights_chat_music(tr::now) },
{ Flag::SendVoiceMessages, tr::lng_rights_chat_voice_messages(tr::now) },
{ Flag::SendFiles, tr::lng_rights_chat_files(tr::now) },
{ Flag::SendStickers { Flag::SendStickers
| Flag::SendGifs | Flag::SendGifs
| Flag::SendGames | Flag::SendGames
| Flag::SendInline, tr::lng_rights_chat_send_stickers(tr::now) }, | Flag::SendInline, tr::lng_rights_chat_stickers(tr::now) },
{ Flag::EmbedLinks, tr::lng_rights_chat_send_links(tr::now) }, { Flag::EmbedLinks, tr::lng_rights_chat_send_links(tr::now) },
{ Flag::SendPolls, tr::lng_rights_chat_send_polls(tr::now) }, { Flag::SendPolls, tr::lng_rights_chat_send_polls(tr::now) },
{ Flag::AddParticipants, tr::lng_rights_chat_add_members(tr::now) }, { Flag::AddParticipants, tr::lng_rights_chat_add_members(tr::now) },

View file

@ -114,6 +114,41 @@ rpl::producer<QString> FieldPlaceholder(
} // namespace } // namespace
SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
using Flag = SendFilesAllow;
using Restriction = ChatRestriction;
const auto allowByRestriction = [&](Restriction check, Flag allow) {
return Data::RestrictionError(peer, check) ? Flag() : allow;
};
return Flag()
| (peer->slowmodeApplied() ? Flag::OnlyOne : Flag())
| (Data::AllowEmojiWithoutPremium(peer)
? Flag::EmojiWithoutPremium
: Flag())
| allowByRestriction(Restriction::SendPhotos, Flag::Photos)
| allowByRestriction(Restriction::SendVideos, Flag::Videos)
| allowByRestriction(Restriction::SendMusic, Flag::Music)
| allowByRestriction(Restriction::SendFiles, Flag::Files)
| allowByRestriction(Restriction::SendStickers, Flag::Stickers)
| allowByRestriction(Restriction::SendGifs, Flag::Gifs)
| allowByRestriction(Restriction::SendOther, Flag::Texts);
}
SendFilesCheck DefaultCheckForPeer(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer) {
return [=](
const Ui::PreparedFile &file,
bool compress,
bool silent) {
const auto error = Data::FileRestrictionError(peer, file, compress);
if (error && !silent) {
controller->showToast({ *error });
}
return !error.has_value();
};
}
SendFilesBox::Block::Block( SendFilesBox::Block::Block(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<std::vector<Ui::PreparedFile>*> items, not_null<std::vector<Ui::PreparedFile>*> items,
@ -289,16 +324,17 @@ SendFilesBox::SendFilesBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Ui::PreparedList &&list, Ui::PreparedList &&list,
const TextWithTags &caption, const TextWithTags &caption,
not_null<PeerData*> peer, SendFilesLimits limits,
SendFilesCheck check,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Type sendMenuType) SendMenu::Type sendMenuType)
: _controller(controller) : _controller(controller)
, _sendType(sendType) , _sendType(sendType)
, _titleHeight(st::boxTitleHeight) , _titleHeight(st::boxTitleHeight)
, _list(std::move(list)) , _list(std::move(list))
, _sendLimit(peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many) , _limits(limits)
, _sendMenuType(sendMenuType) , _sendMenuType(sendMenuType)
, _allowEmojiWithoutPremium(Data::AllowEmojiWithoutPremium(peer)) , _check(std::move(check))
, _caption(this, st::confirmCaptionArea, Ui::InputField::Mode::MultiLine) , _caption(this, st::confirmCaptionArea, Ui::InputField::Mode::MultiLine)
, _prefilledCaptionText(std::move(caption)) , _prefilledCaptionText(std::move(caption))
, _scroll(this, st::boxScroll) , _scroll(this, st::boxScroll)
@ -419,6 +455,11 @@ void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
{ {
auto sendWay = _sendWay.current(); auto sendWay = _sendWay.current();
sendWay.setHasCompressedStickers(_list.hasSticker()); sendWay.setHasCompressedStickers(_list.hasSticker());
if (_limits & SendFilesAllow::OnlyOne) {
if (_list.files.size() > 1) {
sendWay.setGroupFiles(true);
}
}
_sendWay = sendWay; _sendWay = sendWay;
} }
_inner->resizeToWidth(st::boxWideWidth); _inner->resizeToWidth(st::boxWideWidth);
@ -429,7 +470,8 @@ void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
void SendFilesBox::openDialogToAddFileToAlbum() { void SendFilesBox::openDialogToAddFileToAlbum() {
const auto toastParent = Ui::BoxShow(this).toastParent(); const auto toastParent = Ui::BoxShow(this).toastParent();
const auto checkResult = [=](const Ui::PreparedList &list) { const auto checkResult = [=](const Ui::PreparedList &list) {
if (_sendLimit != SendLimit::One) { if (_check)
if (!(_limits & SendFilesAllow::OnlyOne)) {
return true; return true;
} else if (!_list.canBeSentInSlowmodeWith(list)) { } else if (!_list.canBeSentInSlowmodeWith(list)) {
Ui::Toast::Show(toastParent, tr::lng_slowmode_no_many(tr::now)); Ui::Toast::Show(toastParent, tr::lng_slowmode_no_many(tr::now));
@ -555,20 +597,36 @@ void SendFilesBox::initSendWay() {
_sendWay = [&] { _sendWay = [&] {
auto result = Core::App().settings().sendFilesWay(); auto result = Core::App().settings().sendFilesWay();
result.setHasCompressedStickers(_list.hasSticker()); result.setHasCompressedStickers(_list.hasSticker());
if (_sendLimit == SendLimit::One) { if ((_limits & SendFilesAllow::OnlyOne)
&& (_list.files.size() > 1)) {
result.setGroupFiles(true); result.setGroupFiles(true);
return result; }
} else if (_list.overrideSendImagesAsPhotos == false) { if (_list.overrideSendImagesAsPhotos == false) {
result.setSendImagesAsPhotos(false); if (!(_limits & SendFilesAllow::OnlyOne)
|| !_list.hasSticker()) {
result.setSendImagesAsPhotos(false);
}
return result; return result;
} else if (_list.overrideSendImagesAsPhotos == true) { } else if (_list.overrideSendImagesAsPhotos == true) {
result.setSendImagesAsPhotos(true); result.setSendImagesAsPhotos(true);
const auto silent = true;
if (!checkWithWay(result, silent)) {
result.setSendImagesAsPhotos(false);
}
return result; return result;
} }
const auto silent = true;
if (!checkWithWay(result, silent)) {
result.setSendImagesAsPhotos(!result.sendImagesAsPhotos());
}
return result; return result;
}(); }();
_sendWay.changes( _sendWay.changes(
) | rpl::start_with_next([=](SendFilesWay value) { ) | rpl::start_with_next([=](SendFilesWay value) {
const auto hidden = [&] {
return !_caption || _caption->isHidden();
};
const auto was = hidden();
updateCaptionPlaceholder(); updateCaptionPlaceholder();
updateEmojiPanelGeometry(); updateEmojiPanelGeometry();
for (auto &block : _blocks) { for (auto &block : _blocks) {
@ -577,6 +635,10 @@ void SendFilesBox::initSendWay() {
if (!hasSendMenu()) { if (!hasSendMenu()) {
refreshButtons(); refreshButtons();
} }
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();
}
setInnerFocus(); setInnerFocus();
}, lifetime()); }, lifetime());
} }
@ -589,7 +651,8 @@ void SendFilesBox::updateCaptionPlaceholder() {
if (!_list.canAddCaption( if (!_list.canAddCaption(
way.groupFiles() && way.sendImagesAsPhotos(), way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos()) way.sendImagesAsPhotos())
&& _sendLimit == SendLimit::One) { && ((_limits & SendFilesAllow::OnlyOne)
|| !(_limits & SendFilesAllow::Texts))) {
_caption->hide(); _caption->hide();
if (_emojiToggle) { if (_emojiToggle) {
_emojiToggle->hide(); _emojiToggle->hide();
@ -695,8 +758,8 @@ void SendFilesBox::pushBlock(int from, int till) {
_list.files[index] = std::move(list.files.front()); _list.files[index] = std::move(list.files.front());
}); });
}; };
const auto checkResult = [=](const Ui::PreparedList &list) { const auto checkSlowmode = [=](const Ui::PreparedList &list) {
if (_sendLimit != SendLimit::One) { if (list.files.empty() || !(_limits & SendFilesAllow::OnlyOne)) {
return true; return true;
} }
auto removing = std::move(_list.files[index]); auto removing = std::move(_list.files[index]);
@ -713,6 +776,37 @@ void SendFilesBox::pushBlock(int from, int till) {
} }
return true; return true;
}; };
const auto checkRights = [=](const Ui::PreparedList &list) {
if (list.files.empty()) {
return true;
}
auto removing = std::move(_list.files[index]);
std::swap(_list.files[index], _list.files.back());
_list.files.pop_back();
auto way = _sendWay.current();
const auto has = _list.hasSticker()
|| list.files.front().isSticker();
way.setHasCompressedStickers(has);
if (_limits & SendFilesAllow::OnlyOne) {
way.setGroupFiles(true);
}
const auto silent = true;
if (!checkWith(list, way, silent)
&& (!(_limits & SendFilesAllow::OnlyOne) || !has)) {
way.setSendImagesAsPhotos(!way.sendImagesAsPhotos());
}
const auto result = checkWith(list, way);
_list.files.push_back(std::move(removing));
std::swap(_list.files[index], _list.files.back());
if (!result) {
return false;
}
_sendWay = way;
return true;
};
const auto checkResult = [=](const Ui::PreparedList &list) {
return checkSlowmode(list) && checkRights(list);
};
const auto callback = [=](FileDialog::OpenResult &&result) { const auto callback = [=](FileDialog::OpenResult &&result) {
const auto premium = _controller->session().premium(); const auto premium = _controller->session().premium();
FileDialogCallback( FileDialogCallback(
@ -767,7 +861,7 @@ void SendFilesBox::setupSendWayControls() {
_sendImagesAsPhotos.create( _sendImagesAsPhotos.create(
this, this,
tr::lng_send_compressed(tr::now), tr::lng_send_compressed(tr::now),
asPhotosFirst, _sendWay.current().sendImagesAsPhotos(),
st::defaultBoxCheckbox); st::defaultBoxCheckbox);
_sendWay.changes( _sendWay.changes(
@ -777,17 +871,35 @@ void SendFilesBox::setupSendWayControls() {
}, lifetime()); }, lifetime());
_groupFiles->checkedChanges( _groupFiles->checkedChanges(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=](bool checked) {
auto sendWay = _sendWay.current(); auto sendWay = _sendWay.current();
sendWay.setGroupFiles(_groupFiles->checked()); if (sendWay.groupFiles() == checked) {
_sendWay = sendWay; return;
}
sendWay.setGroupFiles(checked);
if (checkWithWay(sendWay)) {
_sendWay = sendWay;
} else {
Ui::PostponeCall(_groupFiles.data(), [=] {
_groupFiles->setChecked(!checked);
});
}
}, lifetime()); }, lifetime());
_sendImagesAsPhotos->checkedChanges( _sendImagesAsPhotos->checkedChanges(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=](bool checked) {
auto sendWay = _sendWay.current(); auto sendWay = _sendWay.current();
sendWay.setSendImagesAsPhotos(_sendImagesAsPhotos->checked()); if (sendWay.sendImagesAsPhotos() == checked) {
_sendWay = sendWay; return;
}
sendWay.setSendImagesAsPhotos(checked);
if (checkWithWay(sendWay)) {
_sendWay = sendWay;
} else {
Ui::PostponeCall(_sendImagesAsPhotos.data(), [=] {
_sendImagesAsPhotos->setChecked(!checked);
});
}
}, lifetime()); }, lifetime());
_wayRemember.create( _wayRemember.create(
@ -811,8 +923,29 @@ void SendFilesBox::setupSendWayControls() {
st::editMediaHintLabel); st::editMediaHintLabel);
} }
bool SendFilesBox::checkWithWay(Ui::SendFilesWay way, bool silent) const {
return checkWith({}, way, silent);
}
bool SendFilesBox::checkWith(
const Ui::PreparedList &added,
Ui::SendFilesWay way,
bool silent) const {
if (!_check) {
return true;
}
const auto compress = way.sendImagesAsPhotos();
auto &already = _list.files;
for (const auto &file : ranges::views::concat(already, added.files)) {
if (!_check(file, compress, silent)) {
return false;
}
}
return true;
}
void SendFilesBox::updateSendWayControls() { void SendFilesBox::updateSendWayControls() {
const auto onlyOne = (_sendLimit == SendLimit::One); const auto onlyOne = (_limits & SendFilesAllow::OnlyOne);
_groupFiles->setVisible(_list.hasGroupOption(onlyOne)); _groupFiles->setVisible(_list.hasGroupOption(onlyOne));
_sendImagesAsPhotos->setVisible( _sendImagesAsPhotos->setVisible(
_list.hasSendImagesAsPhotosOption(onlyOne)); _list.hasSendImagesAsPhotosOption(onlyOne));
@ -828,7 +961,7 @@ void SendFilesBox::updateSendWayControls() {
void SendFilesBox::setupCaption() { void SendFilesBox::setupCaption() {
const auto allow = [=](const auto&) { const auto allow = [=](const auto&) {
return _allowEmojiWithoutPremium; return (_limits & SendFilesAllow::EmojiWithoutPremium);
}; };
InitMessageFieldHandlers( InitMessageFieldHandlers(
_controller, _controller,
@ -899,7 +1032,7 @@ void SendFilesBox::setupEmojiPanel() {
st::emojiPanMinHeight); st::emojiPanMinHeight);
_emojiPanel->hide(); _emojiPanel->hide();
_emojiPanel->selector()->setAllowEmojiWithoutPremium( _emojiPanel->selector()->setAllowEmojiWithoutPremium(
_allowEmojiWithoutPremium); _limits & SendFilesAllow::EmojiWithoutPremium);
_emojiPanel->selector()->emojiChosen( _emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) { ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(_caption->textCursor(), data.emoji); Ui::InsertEmojiAtCursor(_caption->textCursor(), data.emoji);
@ -910,7 +1043,7 @@ void SendFilesBox::setupEmojiPanel() {
if (info if (info
&& info->setType == Data::StickersType::Emoji && info->setType == Data::StickersType::Emoji
&& !_controller->session().premium() && !_controller->session().premium()
&& !_allowEmojiWithoutPremium) { && !(_limits & SendFilesAllow::EmojiWithoutPremium)) {
ShowPremiumPreviewBox( ShowPremiumPreviewBox(
_controller, _controller,
PremiumPreview::AnimatedEmoji); PremiumPreview::AnimatedEmoji);
@ -1026,7 +1159,20 @@ void SendFilesBox::addFile(Ui::PreparedFile &&file) {
// canBeSentInSlowmode checks for non empty filesToProcess. // canBeSentInSlowmode checks for non empty filesToProcess.
auto saved = base::take(_list.filesToProcess); auto saved = base::take(_list.filesToProcess);
_list.files.push_back(std::move(file)); _list.files.push_back(std::move(file));
if (_sendLimit == SendLimit::One && !_list.canBeSentInSlowmode()) { const auto lastOk = [&] {
auto way = _sendWay.current();
if (_limits & SendFilesAllow::OnlyOne) {
way.setGroupFiles(true);
if (!_list.canBeSentInSlowmode()) {
return false;
}
} else if (!checkWithWay(way)) {
return false;
}
_sendWay = way;
return true;
}();
if (!lastOk) {
_list.files.pop_back(); _list.files.pop_back();
} }
_list.filesToProcess = std::move(saved); _list.filesToProcess = std::move(saved);
@ -1058,7 +1204,7 @@ void SendFilesBox::refreshTitleText() {
void SendFilesBox::updateBoxSize() { void SendFilesBox::updateBoxSize() {
auto footerHeight = 0; auto footerHeight = 0;
if (_caption) { if (_caption && !_caption->isHidden()) {
footerHeight += st::boxPhotoCaptionSkip + _caption->height(); footerHeight += st::boxPhotoCaptionSkip + _caption->height();
} }
const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ { const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ {
@ -1113,7 +1259,7 @@ void SendFilesBox::resizeEvent(QResizeEvent *e) {
void SendFilesBox::updateControlsGeometry() { void SendFilesBox::updateControlsGeometry() {
auto bottom = height(); auto bottom = height();
if (_caption) { if (_caption && !_caption->isHidden()) {
_caption->resize(st::sendMediaPreviewSize, _caption->height()); _caption->resize(st::sendMediaPreviewSize, _caption->height());
_caption->moveToLeft( _caption->moveToLeft(
st::boxPhotoPadding.left(), st::boxPhotoPadding.left(),

View file

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include <rpl/variable.h> #include "base/flags.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
@ -48,6 +48,30 @@ namespace SendMenu {
enum class Type; enum class Type;
} // namespace SendMenu } // namespace SendMenu
enum class SendFilesAllow {
OnlyOne = (1 << 0),
Photos = (1 << 1),
Videos = (1 << 2),
Music = (1 << 3),
Files = (1 << 4),
Stickers = (1 << 5),
Gifs = (1 << 6),
EmojiWithoutPremium = (1 << 7),
Texts = (1 << 8),
};
inline constexpr bool is_flag_type(SendFilesAllow) { return true; }
using SendFilesLimits = base::flags<SendFilesAllow>;
using SendFilesCheck = Fn<bool(
const Ui::PreparedFile &file,
bool compress,
bool silent)>;
[[nodiscard]] SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer);
[[nodiscard]] SendFilesCheck DefaultCheckForPeer(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
class SendFilesBox : public Ui::BoxContent { class SendFilesBox : public Ui::BoxContent {
public: public:
enum class SendLimit { enum class SendLimit {
@ -59,7 +83,8 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Ui::PreparedList &&list, Ui::PreparedList &&list,
const TextWithTags &caption, const TextWithTags &caption,
not_null<PeerData*> peer, SendFilesLimits limits,
SendFilesCheck check,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Type sendMenuType); SendMenu::Type sendMenuType);
@ -126,6 +151,13 @@ private:
[[nodiscard]] bool hasSendMenu() const; [[nodiscard]] bool hasSendMenu() const;
[[nodiscard]] bool hasSpoilerMenu() const; [[nodiscard]] bool hasSpoilerMenu() const;
[[nodiscard]] bool allWithSpoilers(); [[nodiscard]] bool allWithSpoilers();
[[nodiscard]] bool checkWithWay(
Ui::SendFilesWay way,
bool silent = false) const;
[[nodiscard]] bool checkWith(
const Ui::PreparedList &added,
Ui::SendFilesWay way,
bool silent = false) const;
void addMenuButton(); void addMenuButton();
void applyBlockChanges(); void applyBlockChanges();
void toggleSpoilers(bool enabled); void toggleSpoilers(bool enabled);
@ -177,10 +209,10 @@ private:
Ui::PreparedList _list; Ui::PreparedList _list;
std::optional<int> _removingIndex; std::optional<int> _removingIndex;
SendLimit _sendLimit = SendLimit::Many; SendFilesLimits _limits = {};
SendMenu::Type _sendMenuType = SendMenu::Type(); SendMenu::Type _sendMenuType = SendMenu::Type();
bool _allowEmojiWithoutPremium = false;
SendFilesCheck _check;
Fn<void( Fn<void(
Ui::PreparedList &&list, Ui::PreparedList &&list,
Ui::SendFilesWay way, Ui::SendFilesWay way,

View file

@ -1120,10 +1120,14 @@ void ShareBox::Inner::chooseForumTopic(not_null<Data::Forum*> forum) {
box->closeBox(); box->closeBox();
}, box->lifetime()); }, box->lifetime());
}; };
auto filter = [=](not_null<Data::ForumTopic*> topic) {
return guard && _descriptor.filterCallback(topic);
};
auto box = Box<PeerListBox>( auto box = Box<PeerListBox>(
std::make_unique<ChooseTopicBoxController>( std::make_unique<ChooseTopicBoxController>(
forum, forum,
std::move(chosen)), std::move(chosen),
std::move(filter)),
std::move(initBox)); std::move(initBox));
*weak = box.data(); *weak = box.data();
_show->showBox(std::move(box)); _show->showBox(std::move(box));
@ -1507,8 +1511,12 @@ void FastShareMessage(
} }
}; };
auto filterCallback = [isGame](not_null<Data::Thread*> thread) { const auto requiredRight = item->requiredSendRight();
return thread->canWrite() const auto requiresInline = item->requiresSendInlineRight();
auto filterCallback = [=](not_null<Data::Thread*> thread) {
return Data::CanSend(thread, requiredRight)
&& (!requiresInline
|| Data::CanSend(thread, ChatRestriction::SendInline))
&& (!isGame || !thread->peer()->isBroadcast()); && (!isGame || !thread->peer()->isBroadcast());
}; };
auto copyLinkCallback = canCopyLink auto copyLinkCallback = canCopyLink

View file

@ -1639,7 +1639,7 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
return rpl::single(false) | rpl::type_erased(); return rpl::single(false) | rpl::type_erased();
} }
return rpl::combine( return rpl::combine(
Data::CanWriteValue(peer, false), Data::CanSendValue(peer, ChatRestriction::SendOther, false),
_call->joinAsValue() _call->joinAsValue()
) | rpl::map([=](bool can, not_null<PeerData*> joinAs) { ) | rpl::map([=](bool can, not_null<PeerData*> joinAs) {
return can && joinAs->isSelf(); return can && joinAs->isSelf();

View file

@ -845,7 +845,7 @@ void Panel::setupMembers() {
_members->addMembersRequests( _members->addMembersRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (!_peer->isBroadcast() if (!_peer->isBroadcast()
&& _peer->canWrite(false) && Data::CanSend(_peer, ChatRestriction::SendOther, false)
&& _call->joinAs()->isSelf()) { && _call->joinAs()->isSelf()) {
addMembers(); addMembers();
} else if (const auto channel = _peer->asChannel()) { } else if (const auto channel = _peer->asChannel()) {

View file

@ -194,7 +194,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
showToast(tr::lng_share_done(tr::now)); showToast(tr::lng_share_done(tr::now));
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) { auto filterCallback = [](not_null<Data::Thread*> thread) {
return thread->canWrite(); return Data::CanSend(thread, ChatRestriction::SendOther);
}; };
const auto scheduleStyle = [&] { const auto scheduleStyle = [&] {

View file

@ -388,7 +388,9 @@ TabbedSelector::TabbedSelector(
_tabsSlider->raise(); _tabsSlider->raise();
} }
if (hasStickersTab() || hasGifsTab()) { if (hasStickersTab()
|| hasGifsTab()
|| (hasEmojiTab() && _mode == Mode::Full)) {
session().changes().peerUpdates( session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights Data::PeerUpdate::Flag::Rights
) | rpl::filter([=](const Data::PeerUpdate &update) { ) | rpl::filter([=](const Data::PeerUpdate &update) {
@ -892,6 +894,14 @@ void TabbedSelector::checkRestrictedPeer() {
? Data::RestrictionError( ? Data::RestrictionError(
_currentPeer, _currentPeer,
ChatRestriction::SendGifs) ChatRestriction::SendGifs)
: (_currentTabType == SelectorTab::Emoji && _mode == Mode::Full)
? (Data::RestrictionError(
_currentPeer,
ChatRestriction::SendInline)
? Data::RestrictionError(
_currentPeer,
ChatRestriction::SendOther)
: std::nullopt)
: std::nullopt; : std::nullopt;
if (error) { if (error) {
if (!_restrictedLabel) { if (!_restrictedLabel) {

View file

@ -321,13 +321,18 @@ ChatRestrictionsInfo ChannelData::KickedRestrictedRights(
not_null<PeerData*> participant) { not_null<PeerData*> participant) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
const auto flags = Flag::ViewMessages const auto flags = Flag::ViewMessages
| Flag::SendMessages
| Flag::SendMedia
| Flag::EmbedLinks
| Flag::SendStickers | Flag::SendStickers
| Flag::SendGifs | Flag::SendGifs
| Flag::SendGames | Flag::SendGames
| Flag::SendInline; | Flag::SendInline
| Flag::SendPhotos
| Flag::SendVideos
| Flag::SendVideoMessages
| Flag::SendMusic
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther
| Flag::EmbedLinks;
return ChatRestrictionsInfo( return ChatRestrictionsInfo(
(participant->isUser() ? flags : Flag::ViewMessages), (participant->isUser() ? flags : Flag::ViewMessages),
std::numeric_limits<int32>::max()); std::numeric_limits<int32>::max());
@ -549,10 +554,6 @@ bool ChannelData::canAddMembers() const {
: ((adminRights() & AdminRight::InviteByLinkOrAdd) || amCreator()); : ((adminRights() & AdminRight::InviteByLinkOrAdd) || amCreator());
} }
bool ChannelData::canSendPolls() const {
return canWrite() && !amRestricted(ChatRestriction::SendPolls);
}
bool ChannelData::canAddAdmins() const { bool ChannelData::canAddAdmins() const {
return amCreator() return amCreator()
|| (adminRights() & AdminRight::AddAdmins); || (adminRights() & AdminRight::AddAdmins);
@ -563,18 +564,6 @@ bool ChannelData::canPublish() const {
|| (adminRights() & AdminRight::PostMessages); || (adminRights() & AdminRight::PostMessages);
} }
bool ChannelData::canWrite(bool checkForForum) const {
// Duplicated in Data::CanWriteValue().
const auto allowed = amIn()
|| ((flags() & Flag::HasLink) && !(flags() & Flag::JoinToWrite));
const auto forumRestriction = checkForForum && isForum();
return allowed
&& !forumRestriction
&& (canPublish()
|| (!isBroadcast()
&& !amRestricted(Restriction::SendMessages)));
}
bool ChannelData::allowsForwarding() const { bool ChannelData::allowsForwarding() const {
return !(flags() & Flag::NoForwards); return !(flags() & Flag::NoForwards);
} }

View file

@ -318,7 +318,6 @@ public:
void setDefaultRestrictions(ChatRestrictions rights); void setDefaultRestrictions(ChatRestrictions rights);
// Like in ChatData. // Like in ChatData.
[[nodiscard]] bool canWrite(bool checkForForum = true) const;
[[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditInformation() const;
[[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditPermissions() const;
@ -327,7 +326,6 @@ public:
[[nodiscard]] bool canAddMembers() const; [[nodiscard]] bool canAddMembers() const;
[[nodiscard]] bool canAddAdmins() const; [[nodiscard]] bool canAddAdmins() const;
[[nodiscard]] bool canBanMembers() const; [[nodiscard]] bool canBanMembers() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] bool anyoneCanAddMembers() const; [[nodiscard]] bool anyoneCanAddMembers() const;
[[nodiscard]] bool canEditMessages() const; [[nodiscard]] bool canEditMessages() const;

View file

@ -63,11 +63,6 @@ ChatAdminRightsInfo ChatData::defaultAdminRights(not_null<UserData*> user) {
| (isCreator ? Flag::AddAdmins : Flag(0))); | (isCreator ? Flag::AddAdmins : Flag(0)));
} }
bool ChatData::canWrite() const {
// Duplicated in Data::CanWriteValue().
return amIn() && !amRestricted(ChatRestriction::SendMessages);
}
bool ChatData::allowsForwarding() const { bool ChatData::allowsForwarding() const {
return !(flags() & Flag::NoForwards); return !(flags() & Flag::NoForwards);
} }
@ -99,10 +94,6 @@ bool ChatData::canAddMembers() const {
return amIn() && !amRestricted(ChatRestriction::AddParticipants); return amIn() && !amRestricted(ChatRestriction::AddParticipants);
} }
bool ChatData::canSendPolls() const {
return amIn() && !amRestricted(ChatRestriction::SendPolls);
}
bool ChatData::canAddAdmins() const { bool ChatData::canAddAdmins() const {
return amIn() && amCreator(); return amIn() && amCreator();
} }

View file

@ -100,7 +100,6 @@ public:
not_null<UserData*> user); not_null<UserData*> user);
// Like in ChannelData. // Like in ChannelData.
[[nodiscard]] bool canWrite() const;
[[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditInformation() const;
[[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditPermissions() const;
@ -110,7 +109,6 @@ public:
[[nodiscard]] bool canAddMembers() const; [[nodiscard]] bool canAddMembers() const;
[[nodiscard]] bool canAddAdmins() const; [[nodiscard]] bool canAddAdmins() const;
[[nodiscard]] bool canBanMembers() const; [[nodiscard]] bool canBanMembers() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] bool anyoneCanAddMembers() const; [[nodiscard]] bool anyoneCanAddMembers() const;
void applyEditAdmin(not_null<UserData*> user, bool isAdmin); void applyEditAdmin(not_null<UserData*> user, bool isAdmin);

View file

@ -7,7 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "data/data_chat_participant_status.h" #include "data/data_chat_participant_status.h"
#include "base/unixtime.h"
#include "boxes/peers/edit_peer_permissions_box.h" #include "boxes/peers/edit_peer_permissions_box.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_peer_values.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "ui/chat/attach/attach_prepare.h"
namespace { namespace {
@ -53,4 +61,313 @@ std::vector<ChatRestrictions> ListOfRestrictions(
| ranges::to_vector; | ranges::to_vector;
} }
ChatRestrictions AllSendRestrictions() {
constexpr auto result = [] {
auto result = ChatRestrictions();
for (const auto right : AllSendRestrictionsList()) {
result |= right;
}
return result;
}();
return result;
}
ChatRestrictions FilesSendRestrictions() {
constexpr auto result = [] {
auto result = ChatRestrictions();
for (const auto right : FilesSendRestrictionsList()) {
result |= right;
}
return result;
}();
return result;
}
ChatRestrictions TabbedPanelSendRestrictions() {
constexpr auto result = [] {
auto result = ChatRestrictions();
for (const auto right : TabbedPanelSendRestrictionsList()) {
result |= right;
}
return result;
}();
return result;
}
// Duplicated in CanSendAnyOfValue().
bool CanSendAnyOf(
not_null<Thread*> thread,
ChatRestrictions rights,
bool forbidInForums) {
const auto peer = thread->peer();
const auto topic = thread->asTopic();
return CanSendAnyOf(peer, rights, forbidInForums && !topic)
&& (!topic || !topic->closed() || topic->canToggleClosed());
}
// Duplicated in CanSendAnyOfValue().
bool CanSendAnyOf(
not_null<PeerData*> peer,
ChatRestrictions rights,
bool forbidInForums) {
if (const auto user = peer->asUser()) {
if (user->isInaccessible() || user->isRepliesChat()) {
return false;
} else if (rights
& ~(ChatRestriction::SendVoiceMessages
| ChatRestriction::SendVideoMessages
| ChatRestriction::SendPolls)) {
return true;
}
for (const auto right : {
ChatRestriction::SendVoiceMessages,
ChatRestriction::SendVideoMessages,
ChatRestriction::SendPolls,
}) {
if ((rights & right) && !user->amRestricted(right)) {
return true;
}
}
return false;
} else if (const auto chat = peer->asChat()) {
if (!chat->amIn()) {
return false;
}
for (const auto right : AllSendRestrictionsList()) {
if ((rights & right) && !chat->amRestricted(right)) {
return true;
}
}
return false;
} else if (const auto channel = peer->asChannel()) {
using Flag = ChannelDataFlag;
const auto allowed = channel->amIn()
|| ((channel->flags() & Flag::HasLink)
&& !(channel->flags() & Flag::JoinToWrite));
if (!allowed || (forbidInForums && channel->isForum())) {
return false;
} else if (channel->canPublish()) {
return true;
} else if (channel->isBroadcast()) {
return false;
}
for (const auto right : AllSendRestrictionsList()) {
if ((rights & right) && !channel->amRestricted(right)) {
return true;
}
}
return false;
}
Unexpected("Peer type in CanSendAnyOf.");
}
std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
ChatRestriction restriction) {
using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) {
const auto result = (restriction == Flag::SendVoiceMessages)
? tr::lng_restricted_send_voice_messages(
tr::now,
lt_user,
user->name())
: (restriction == Flag::SendVoiceMessages)
? tr::lng_restricted_send_video_messages(
tr::now,
lt_user,
user->name())
: (restriction == Flag::SendPolls)
? u"can't send polls :("_q
: (restriction == Flag::PinMessages)
? u"can't pin :("_q
: std::optional<QString>();
Ensures(result.has_value());
return result;
}
const auto all = restricted.isWithEveryone();
const auto channel = peer->asChannel();
if (!all && channel) {
auto restrictedUntil = channel->restrictedUntil();
if (restrictedUntil > 0
&& !ChannelData::IsRestrictedForever(restrictedUntil)) {
auto restrictedUntilDateTime = base::unixtime::parse(
channel->restrictedUntil());
auto date = QLocale().toString(
restrictedUntilDateTime.date(),
QLocale::ShortFormat);
auto time = QLocale().toString(
restrictedUntilDateTime.time(),
QLocale::ShortFormat);
switch (restriction) {
case Flag::SendPolls:
return tr::lng_restricted_send_polls_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendOther:
return tr::lng_restricted_send_message_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendPhotos:
return tr::lng_restricted_send_photos_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendVideos:
return tr::lng_restricted_send_videos_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendMusic:
return tr::lng_restricted_send_music_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendFiles:
return tr::lng_restricted_send_files_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendVideoMessages:
return tr::lng_restricted_send_video_messages_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendVoiceMessages:
return tr::lng_restricted_send_voice_messages_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendStickers:
return tr::lng_restricted_send_stickers_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendGifs:
return tr::lng_restricted_send_gifs_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendInline:
case Flag::SendGames:
return tr::lng_restricted_send_inline_until(
tr::now, lt_date, date, lt_time, time);
}
Unexpected("Restriction in Data::RestrictionErrorKey.");
}
}
switch (restriction) {
case Flag::SendPolls:
return all
? tr::lng_restricted_send_polls_all(tr::now)
: tr::lng_restricted_send_polls(tr::now);
case Flag::SendOther:
return all
? tr::lng_restricted_send_message_all(tr::now)
: tr::lng_restricted_send_message(tr::now);
case Flag::SendPhotos:
return all
? tr::lng_restricted_send_photos_all(tr::now)
: tr::lng_restricted_send_photos(tr::now);
case Flag::SendVideos:
return all
? tr::lng_restricted_send_videos_all(tr::now)
: tr::lng_restricted_send_videos(tr::now);
case Flag::SendMusic:
return all
? tr::lng_restricted_send_music_all(tr::now)
: tr::lng_restricted_send_music(tr::now);
case Flag::SendFiles:
return all
? tr::lng_restricted_send_files_all(tr::now)
: tr::lng_restricted_send_files(tr::now);
case Flag::SendVideoMessages:
return all
? tr::lng_restricted_send_video_messages_all(tr::now)
: tr::lng_restricted_send_video_messages_group(tr::now);
case Flag::SendVoiceMessages:
return all
? tr::lng_restricted_send_voice_messages_all(tr::now)
: tr::lng_restricted_send_voice_messages_group(tr::now);
case Flag::SendStickers:
return all
? tr::lng_restricted_send_stickers_all(tr::now)
: tr::lng_restricted_send_stickers(tr::now);
case Flag::SendGifs:
return all
? tr::lng_restricted_send_gifs_all(tr::now)
: tr::lng_restricted_send_gifs(tr::now);
case Flag::SendInline:
case Flag::SendGames:
return all
? tr::lng_restricted_send_inline_all(tr::now)
: tr::lng_restricted_send_inline(tr::now);
}
Unexpected("Restriction in Data::RestrictionErrorKey.");
}
return std::nullopt;
}
std::optional<QString> AnyFileRestrictionError(not_null<PeerData*> peer) {
using Restriction = ChatRestriction;
for (const auto right : FilesSendRestrictionsList()) {
if (!RestrictionError(peer, right)) {
return {};
}
}
return RestrictionError(peer, Restriction::SendFiles);
}
std::optional<QString> FileRestrictionError(
not_null<PeerData*> peer,
const Ui::PreparedList &list,
std::optional<bool> compress) {
const auto slowmode = peer->slowmodeApplied();
if (slowmode) {
if (!list.canBeSentInSlowmode()) {
return tr::lng_slowmode_no_many(tr::now);
} else if (list.files.size() > 1 && list.hasSticker()) {
if (compress == false) {
return tr::lng_slowmode_no_many(tr::now);
} else {
compress = true;
}
}
}
for (const auto &file : list.files) {
if (const auto error = FileRestrictionError(peer, file, compress)) {
return error;
}
}
return {};
}
std::optional<QString> FileRestrictionError(
not_null<PeerData*> peer,
const Ui::PreparedFile &file,
std::optional<bool> compress) {
using Type = Ui::PreparedFile::Type;
using Restriction = ChatRestriction;
const auto stickers = RestrictionError(peer, Restriction::SendStickers);
const auto gifs = RestrictionError(peer, Restriction::SendGifs);
const auto photos = RestrictionError(peer, Restriction::SendPhotos);
const auto videos = RestrictionError(peer, Restriction::SendVideos);
const auto music = RestrictionError(peer, Restriction::SendMusic);
const auto files = RestrictionError(peer, Restriction::SendFiles);
if (!stickers && !gifs && !photos && !videos && !music && !files) {
return {};
}
switch (file.type) {
case Type::Photo:
if (compress == true && photos) {
return photos;
} else if (const auto other = file.isSticker() ? stickers : files) {
if ((compress == false || photos) && other) {
return (file.isSticker() || !photos) ? other : photos;
}
}
break;
case Type::Video:
if (const auto error = file.isGifv() ? gifs : videos) {
return error;
}
break;
case Type::Music:
if (music) {
return music;
}
break;
case Type::File:
if (files) {
return files;
}
break;
}
return {};
}
} // namespace Data } // namespace Data

View file

@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
enum class UserRestriction { namespace Ui {
SendVoiceMessages, struct PreparedList;
SendVideoMessages, struct PreparedFile;
}; } // namespace Ui
enum class ChatAdminRight { enum class ChatAdminRight {
ChangeInfo = (1 << 0), ChangeInfo = (1 << 0),
@ -31,14 +31,22 @@ using ChatAdminRights = base::flags<ChatAdminRight>;
enum class ChatRestriction { enum class ChatRestriction {
ViewMessages = (1 << 0), ViewMessages = (1 << 0),
SendMessages = (1 << 1),
SendMedia = (1 << 2),
SendStickers = (1 << 3), SendStickers = (1 << 3),
SendGifs = (1 << 4), SendGifs = (1 << 4),
SendGames = (1 << 5), SendGames = (1 << 5),
SendInline = (1 << 6), SendInline = (1 << 6),
EmbedLinks = (1 << 7),
SendPolls = (1 << 8), SendPolls = (1 << 8),
SendPhotos = (1 << 19),
SendVideos = (1 << 20),
SendVideoMessages = (1 << 21),
SendMusic = (1 << 22),
SendVoiceMessages = (1 << 23),
SendFiles = (1 << 24),
SendOther = (1 << 25),
EmbedLinks = (1 << 7),
ChangeInfo = (1 << 10), ChangeInfo = (1 << 10),
AddParticipants = (1 << 15), AddParticipants = (1 << 15),
PinMessages = (1 << 17), PinMessages = (1 << 17),
@ -70,6 +78,8 @@ struct ChatRestrictionsInfo {
namespace Data { namespace Data {
class Thread;
struct AdminRightsSetOptions { struct AdminRightsSetOptions {
bool isGroup : 1 = false; bool isGroup : 1 = false;
bool isForum : 1 = false; bool isForum : 1 = false;
@ -83,4 +93,97 @@ struct RestrictionsSetOptions {
[[nodiscard]] std::vector<ChatRestrictions> ListOfRestrictions( [[nodiscard]] std::vector<ChatRestrictions> ListOfRestrictions(
RestrictionsSetOptions options); RestrictionsSetOptions options);
[[nodiscard]] inline constexpr auto AllSendRestrictionsList() {
return std::array{
ChatRestriction::SendOther,
ChatRestriction::SendStickers,
ChatRestriction::SendGifs,
ChatRestriction::SendGames,
ChatRestriction::SendInline,
ChatRestriction::SendPolls,
ChatRestriction::SendPhotos,
ChatRestriction::SendVideos,
ChatRestriction::SendVideoMessages,
ChatRestriction::SendMusic,
ChatRestriction::SendVoiceMessages,
ChatRestriction::SendFiles,
};
}
[[nodiscard]] inline constexpr auto FilesSendRestrictionsList() {
return std::array{
ChatRestriction::SendStickers,
ChatRestriction::SendGifs,
ChatRestriction::SendPhotos,
ChatRestriction::SendVideos,
ChatRestriction::SendMusic,
ChatRestriction::SendFiles,
};
}
[[nodiscard]] inline constexpr auto TabbedPanelSendRestrictionsList() {
return std::array{
ChatRestriction::SendStickers,
ChatRestriction::SendGifs,
ChatRestriction::SendOther,
};
}
[[nodiscard]] ChatRestrictions AllSendRestrictions();
[[nodiscard]] ChatRestrictions FilesSendRestrictions();
[[nodiscard]] ChatRestrictions TabbedPanelSendRestrictions();
[[nodiscard]] bool CanSendAnyOf(
not_null<Thread*> thread,
ChatRestrictions rights,
bool forbidInForums = true);
[[nodiscard]] bool CanSendAnyOf(
not_null<PeerData*> peer,
ChatRestrictions rights,
bool forbidInForums = true);
[[nodiscard]] inline bool CanSend(
not_null<Thread*> thread,
ChatRestriction right,
bool forbidInForums = true) {
return CanSendAnyOf(thread, right, forbidInForums);
}
[[nodiscard]] inline bool CanSend(
not_null<PeerData*> peer,
ChatRestriction right,
bool forbidInForums = true) {
return CanSendAnyOf(peer, right, forbidInForums);
}
[[nodiscard]] inline bool CanSendTexts(
not_null<Thread*> thread,
bool forbidInForums = true) {
return CanSend(thread, ChatRestriction::SendOther, forbidInForums);
}
[[nodiscard]] inline bool CanSendTexts(
not_null<PeerData*> peer,
bool forbidInForums = true) {
return CanSend(peer, ChatRestriction::SendOther, forbidInForums);
}
[[nodiscard]] inline bool CanSendAnything(
not_null<Thread*> thread,
bool forbidInForums = true) {
return CanSendAnyOf(thread, AllSendRestrictions(), forbidInForums);
}
[[nodiscard]] inline bool CanSendAnything(
not_null<PeerData*> peer,
bool forbidInForums = true) {
return CanSendAnyOf(peer, AllSendRestrictions(), forbidInForums);
}
[[nodiscard]] std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
ChatRestriction restriction);
[[nodiscard]] std::optional<QString> AnyFileRestrictionError(
not_null<PeerData*> peer);
[[nodiscard]] std::optional<QString> FileRestrictionError(
not_null<PeerData*> peer,
const Ui::PreparedList &list,
std::optional<bool> compress);
[[nodiscard]] std::optional<QString> FileRestrictionError(
not_null<PeerData*> peer,
const Ui::PreparedFile &file,
std::optional<bool> compress);
} // namespace Data } // namespace Data

View file

@ -841,6 +841,22 @@ void DocumentData::setLoadedInMediaCache(bool loaded) {
} }
} }
ChatRestriction DocumentData::requiredSendRight() const {
return isVideoFile()
? ChatRestriction::SendVideos
: isSong()
? ChatRestriction::SendMusic
: isVoiceMessage()
? ChatRestriction::SendVoiceMessages
: isVideoMessage()
? ChatRestriction::SendVideoMessages
: sticker()
? ChatRestriction::SendStickers
: isAnimation()
? ChatRestriction::SendGifs
: ChatRestriction::SendFiles;
}
void DocumentData::setFileName(const QString &remoteFileName) { void DocumentData::setFileName(const QString &remoteFileName) {
_filename = remoteFileName; _filename = remoteFileName;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_location.h" #include "core/file_location.h"
#include "ui/image/image.h" #include "ui/image/image.h"
enum class ChatRestriction;
class mtpFileLoader; class mtpFileLoader;
namespace Images { namespace Images {
@ -126,6 +127,8 @@ public:
[[nodiscard]] bool loadedInMediaCache() const; [[nodiscard]] bool loadedInMediaCache() const;
void setLoadedInMediaCache(bool loaded); void setLoadedInMediaCache(bool loaded);
[[nodiscard]] ChatRestriction requiredSendRight() const;
void setWaitingForAlbum(); void setWaitingForAlbum();
[[nodiscard]] bool waitingForAlbum() const; [[nodiscard]] bool waitingForAlbum() const;

View file

@ -249,18 +249,6 @@ bool ForumTopic::my() const {
return (_flags & Flag::My); return (_flags & Flag::My);
} }
bool ForumTopic::canWrite() const {
const auto channel = this->channel();
return channel->amIn()
&& !channel->amRestricted(ChatRestriction::SendMessages)
&& (!closed() || canToggleClosed());
}
bool ForumTopic::canSendPolls() const {
return canWrite()
&& !channel()->amRestricted(ChatRestriction::SendPolls);
}
bool ForumTopic::canEdit() const { bool ForumTopic::canEdit() const {
return my() || channel()->canManageTopics(); return my() || channel()->canManageTopics();
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flags.h" #include "base/flags.h"
class ChannelData; class ChannelData;
enum class ChatRestriction;
namespace style { namespace style {
struct ForumTopicIcon; struct ForumTopicIcon;
@ -80,8 +81,6 @@ public:
[[nodiscard]] not_null<HistoryView::ListMemento*> listMemento(); [[nodiscard]] not_null<HistoryView::ListMemento*> listMemento();
[[nodiscard]] bool my() const; [[nodiscard]] bool my() const;
[[nodiscard]] bool canWrite() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] bool canEdit() const; [[nodiscard]] bool canEdit() const;
[[nodiscard]] bool canToggleClosed() const; [[nodiscard]] bool canToggleClosed() const;
[[nodiscard]] bool canTogglePinned() const; [[nodiscard]] bool canTogglePinned() const;

View file

@ -460,10 +460,6 @@ bool Media::forceForwardedInfo() const {
return false; return false;
} }
QString Media::errorTextForForward(not_null<PeerData*> peer) const {
return QString();
}
bool Media::hasSpoiler() const { bool Media::hasSpoiler() const {
return false; return false;
} }
@ -686,13 +682,6 @@ bool MediaPhoto::allowsEditMedia() const {
return true; return true;
} }
QString MediaPhoto::errorTextForForward(not_null<PeerData*> peer) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendMedia
).value_or(QString());
}
bool MediaPhoto::hasSpoiler() const { bool MediaPhoto::hasSpoiler() const {
return _spoiler; return _spoiler;
} }
@ -1040,46 +1029,6 @@ bool MediaFile::dropForwardedInfo() const {
return _document->isSong(); return _document->isSong();
} }
QString MediaFile::errorTextForForward(not_null<PeerData*> peer) const {
if (const auto sticker = _document->sticker()) {
if (const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendStickers)) {
return *error;
}
} else if (_document->isAnimation()) {
if (_document->isVideoMessage()) {
if (const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendMedia)) {
return *error;
}
if (const auto error = Data::RestrictionError(
peer,
UserRestriction::SendVideoMessages)) {
return *error;
}
} else {
if (const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendGifs)) {
return *error;
}
}
} else if (const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendMedia)) {
return *error;
} else if (_document->isVoiceMessage()) {
if (const auto error = Data::RestrictionError(
peer,
UserRestriction::SendVoiceMessages)) {
return *error;
}
}
return QString();
}
bool MediaFile::hasSpoiler() const { bool MediaFile::hasSpoiler() const {
return _spoiler; return _spoiler;
} }
@ -1589,13 +1538,6 @@ TextForMimeData MediaGame::clipboardText() const {
return TextForMimeData(); return TextForMimeData();
} }
QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendGames
).value_or(QString());
}
bool MediaGame::dropForwardedInfo() const { bool MediaGame::dropForwardedInfo() const {
return true; return true;
} }
@ -1773,16 +1715,6 @@ TextForMimeData MediaPoll::clipboardText() const {
return TextForMimeData::Simple(text); return TextForMimeData::Simple(text);
} }
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
if (_poll->publicVotes() && peer->isChannel() && !peer->isMegagroup()) {
return tr::lng_restricted_send_public_polls(tr::now);
}
return Data::RestrictionError(
peer,
ChatRestriction::SendPolls
).value_or(QString());
}
bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) {
return false; return false;
} }
@ -1888,7 +1820,7 @@ ClickHandlerPtr MediaDice::MakeHandler(
.durationMs = Ui::Toast::kDefaultDuration * 2, .durationMs = Ui::Toast::kDefaultDuration * 2,
.multiline = true, .multiline = true,
}; };
if (history->peer->canWrite()) { if (Data::CanSend(history->peer, ChatRestriction::SendOther)) {
auto link = Ui::Text::Link( auto link = Ui::Text::Link(
tr::lng_about_random_send(tr::now).toUpper()); tr::lng_about_random_send(tr::now).toUpper());
link.entities.push_back( link.entities.push_back(

View file

@ -128,7 +128,6 @@ public:
virtual bool forwardedBecomesUnread() const; virtual bool forwardedBecomesUnread() const;
virtual bool dropForwardedInfo() const; virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const; virtual bool forceForwardedInfo() const;
virtual QString errorTextForForward(not_null<PeerData*> peer) const;
[[nodiscard]] virtual bool hasSpoiler() const; [[nodiscard]] virtual bool hasSpoiler() const;
[[nodiscard]] virtual bool consumeMessageText( [[nodiscard]] virtual bool consumeMessageText(
@ -190,7 +189,6 @@ public:
TextForMimeData clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsEditCaption() const override; bool allowsEditCaption() const override;
bool allowsEditMedia() const override; bool allowsEditMedia() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool hasSpoiler() const override; bool hasSpoiler() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -234,7 +232,6 @@ public:
bool allowsEditMedia() const override; bool allowsEditMedia() const override;
bool forwardedBecomesUnread() const override; bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override; bool dropForwardedInfo() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool hasSpoiler() const override; bool hasSpoiler() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -395,7 +392,6 @@ public:
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override; TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool dropForwardedInfo() const override; bool dropForwardedInfo() const override;
bool consumeMessageText(const TextWithEntities &text) override; bool consumeMessageText(const TextWithEntities &text) override;
@ -460,7 +456,6 @@ public:
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override; TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;

View file

@ -462,7 +462,7 @@ QString PeerData::computeUnavailableReason() const {
// This is duplicated in CanPinMessagesValue(). // This is duplicated in CanPinMessagesValue().
bool PeerData::canPinMessages() const { bool PeerData::canPinMessages() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return user->flags() & UserDataFlag::CanPinMessages; return !user->amRestricted(ChatRestriction::PinMessages);
} else if (const auto chat = asChat()) { } else if (const auto chat = asChat()) {
return chat->amIn() return chat->amIn()
&& !chat->amRestricted(ChatRestriction::PinMessages); && !chat->amRestricted(ChatRestriction::PinMessages);
@ -881,18 +881,6 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const {
return nullptr; return nullptr;
} }
bool PeerData::canWrite(bool checkForForum) const {
if (const auto user = asUser()) {
return user->canWrite();
} else if (const auto channel = asChannel()) {
return channel->canWrite(checkForForum);
} else if (const auto chat = asChat()) {
return chat->canWrite();
}
return false;
}
bool PeerData::allowsForwarding() const { bool PeerData::allowsForwarding() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return true; return true;
@ -920,10 +908,26 @@ Data::RestrictionCheckResult PeerData::amRestricted(
return chat->hasAdminRights(); return chat->hasAdminRights();
} }
}; };
if (const auto channel = asChannel()) { if (const auto user = asUser()) {
return (right == ChatRestriction::SendVoiceMessages
|| right == ChatRestriction::SendVideoMessages)
? ((user->flags() & UserDataFlag::VoiceMessagesForbidden)
? Result::Explicit()
: Result::Allowed())
: (right == ChatRestriction::SendPolls)
? ((!user->isBot() || user->isSupport())
? Result::Explicit()
: Result::Allowed())
: (right == ChatRestriction::PinMessages)
? ((user->flags() & UserDataFlag::CanPinMessages)
? Result::Allowed()
: Result::Explicit())
: Result::Allowed();
} else if (const auto channel = asChannel()) {
const auto defaultRestrictions = channel->defaultRestrictions() const auto defaultRestrictions = channel->defaultRestrictions()
| (channel->isPublic() | (channel->isPublic()
? (ChatRestriction::PinMessages | ChatRestriction::ChangeInfo) ? (ChatRestriction::PinMessages
| ChatRestriction::ChangeInfo)
: ChatRestrictions(0)); : ChatRestrictions(0));
return (channel->amCreator() || allowByAdminRights(right, channel)) return (channel->amCreator() || allowByAdminRights(right, channel))
? Result::Allowed() ? Result::Allowed()
@ -1010,19 +1014,6 @@ int PeerData::slowmodeSecondsLeft() const {
return 0; return 0;
} }
bool PeerData::canSendPolls() const {
if (const auto user = asUser()) {
return user->isBot()
&& !user->isRepliesChat()
&& !user->isSupport();
} else if (const auto chat = asChat()) {
return chat->canSendPolls();
} else if (const auto channel = asChannel()) {
return channel->canSendPolls();
}
return false;
}
bool PeerData::canManageGroupCall() const { bool PeerData::canManageGroupCall() const {
if (const auto chat = asChat()) { if (const auto chat = asChat()) {
return chat->amCreator() return chat->amCreator()
@ -1109,95 +1100,6 @@ void PeerData::setMessagesTTL(TimeId period) {
namespace Data { namespace Data {
std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
ChatRestriction restriction) {
using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) {
const auto all = restricted.isWithEveryone();
const auto channel = peer->asChannel();
if (!all && channel) {
auto restrictedUntil = channel->restrictedUntil();
if (restrictedUntil > 0 && !ChannelData::IsRestrictedForever(restrictedUntil)) {
auto restrictedUntilDateTime = base::unixtime::parse(channel->restrictedUntil());
auto date = QLocale().toString(restrictedUntilDateTime.date(), QLocale::ShortFormat);
auto time = QLocale().toString(restrictedUntilDateTime.time(), QLocale::ShortFormat);
switch (restriction) {
case Flag::SendPolls:
return tr::lng_restricted_send_polls_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendMessages:
return tr::lng_restricted_send_message_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendMedia:
return tr::lng_restricted_send_media_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendStickers:
return tr::lng_restricted_send_stickers_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendGifs:
return tr::lng_restricted_send_gifs_until(
tr::now, lt_date, date, lt_time, time);
case Flag::SendInline:
case Flag::SendGames:
return tr::lng_restricted_send_inline_until(
tr::now, lt_date, date, lt_time, time);
}
Unexpected("Restriction in Data::RestrictionErrorKey.");
}
}
switch (restriction) {
case Flag::SendPolls:
return all
? tr::lng_restricted_send_polls_all(tr::now)
: tr::lng_restricted_send_polls(tr::now);
case Flag::SendMessages:
return all
? tr::lng_restricted_send_message_all(tr::now)
: tr::lng_restricted_send_message(tr::now);
case Flag::SendMedia:
return all
? tr::lng_restricted_send_media_all(tr::now)
: tr::lng_restricted_send_media(tr::now);
case Flag::SendStickers:
return all
? tr::lng_restricted_send_stickers_all(tr::now)
: tr::lng_restricted_send_stickers(tr::now);
case Flag::SendGifs:
return all
? tr::lng_restricted_send_gifs_all(tr::now)
: tr::lng_restricted_send_gifs(tr::now);
case Flag::SendInline:
case Flag::SendGames:
return all
? tr::lng_restricted_send_inline_all(tr::now)
: tr::lng_restricted_send_inline(tr::now);
}
Unexpected("Restriction in Data::RestrictionErrorKey.");
}
return std::nullopt;
}
std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
UserRestriction restriction) {
const auto user = peer->asUser();
if (user && !user->canReceiveVoices()) {
const auto voice = restriction == UserRestriction::SendVoiceMessages;
if (voice
|| (restriction == UserRestriction::SendVideoMessages)) {
return (voice
? tr::lng_restricted_send_voice_messages
: tr::lng_restricted_send_video_messages)(
tr::now,
lt_user,
user->name());
}
}
return std::nullopt;
}
void SetTopPinnedMessageId( void SetTopPinnedMessageId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId messageId) { MsgId messageId) {

View file

@ -20,7 +20,6 @@ class ChatData;
class ChannelData; class ChannelData;
enum class ChatRestriction; enum class ChatRestriction;
enum class UserRestriction;
namespace Ui { namespace Ui {
class EmptyUserpic; class EmptyUserpic;
@ -205,7 +204,6 @@ public:
return _notify; return _notify;
} }
[[nodiscard]] bool canWrite(bool checkForForum = true) const;
[[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] Data::RestrictionCheckResult amRestricted( [[nodiscard]] Data::RestrictionCheckResult amRestricted(
ChatRestriction right) const; ChatRestriction right) const;
@ -214,7 +212,6 @@ public:
[[nodiscard]] bool slowmodeApplied() const; [[nodiscard]] bool slowmodeApplied() const;
[[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const; [[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const;
[[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] int slowmodeSecondsLeft() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] bool canManageGroupCall() const; [[nodiscard]] bool canManageGroupCall() const;
[[nodiscard]] UserData *asUser(); [[nodiscard]] UserData *asUser();
@ -454,14 +451,6 @@ private:
namespace Data { namespace Data {
std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
ChatRestriction restriction);
std::optional<QString> RestrictionError(
not_null<PeerData*> peer,
UserRestriction restriction);
void SetTopPinnedMessageId( void SetTopPinnedMessageId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId messageId); MsgId messageId);

View file

@ -164,129 +164,136 @@ inline auto DefaultRestrictionValue(
return SingleFlagValue(DefaultRestrictionsValue(chat), flag); return SingleFlagValue(DefaultRestrictionsValue(chat), flag);
} }
rpl::producer<bool> CanWriteValue(UserData *user) { // Duplicated in CanSendAnyOf().
using namespace rpl::mappers; [[nodiscard]] rpl::producer<bool> CanSendAnyOfValue(
not_null<Thread*> thread,
if (user->isRepliesChat()) { ChatRestrictions rights,
return rpl::single(false); bool forbidInForums) {
if (const auto topic = thread->asTopic()) {
using Flag = ChannelDataFlag;
const auto mask = Flag()
| Flag::Left
| Flag::JoinToWrite
| Flag::HasLink
| Flag::Forbidden
| Flag::Creator;
const auto channel = topic->channel();
return rpl::combine(
PeerFlagsValue(channel.get(), mask),
RestrictionsValue(channel, rights),
DefaultRestrictionsValue(channel, rights),
AdminRightsValue(channel, ChatAdminRight::ManageTopics),
topic->session().changes().topicFlagsValue(
topic,
TopicUpdate::Flag::Closed),
[=](
ChannelDataFlags flags,
ChatRestrictions sendRestriction,
ChatRestrictions defaultSendRestriction,
auto,
auto) {
const auto notAmInFlags = Flag::Left | Flag::Forbidden;
const auto allowed = !(flags & notAmInFlags)
|| ((flags & Flag::HasLink)
&& !(flags & Flag::JoinToWrite));
return allowed
&& ((flags & Flag::Creator)
|| (!sendRestriction && !defaultSendRestriction))
&& (!topic->closed() || topic->canToggleClosed());
});
} }
return PeerFlagValue(user, UserDataFlag::Deleted) return CanSendAnyOfValue(thread->peer(), rights, forbidInForums);
| rpl::map(!_1);
} }
rpl::producer<bool> CanWriteValue(ChatData *chat) { // Duplicated in CanSendAnyOf().
using namespace rpl::mappers; [[nodiscard]] rpl::producer<bool> CanSendAnyOfValue(
const auto mask = 0 not_null<PeerData*> peer,
| ChatDataFlag::Deactivated ChatRestrictions rights,
| ChatDataFlag::Forbidden bool forbidInForums) {
| ChatDataFlag::Left if (const auto user = peer->asUser()) {
| ChatDataFlag::Creator; if (user->isRepliesChat()) {
return rpl::combine( return rpl::single(false);
PeerFlagsValue(chat, mask), }
AdminRightsValue(chat), using namespace rpl::mappers;
DefaultRestrictionValue( const auto other = rights & ~(ChatRestriction::SendPolls
chat, | ChatRestriction::SendVoiceMessages
ChatRestriction::SendMessages), | ChatRestriction::SendVideoMessages);
[]( if (other) {
return PeerFlagValue(user, UserDataFlag::Deleted)
| rpl::map(!_1);
} else if (rights & ChatRestriction::SendPolls) {
if (CanSend(user, ChatRestriction::SendPolls)) {
return PeerFlagValue(user, UserDataFlag::Deleted)
| rpl::map(!_1);
} else if (rights == ChatRestriction::SendPolls) {
return rpl::single(false);
}
}
const auto mask = UserDataFlag::Deleted
| UserDataFlag::VoiceMessagesForbidden;
return PeerFlagsValue(user, mask) | rpl::map(!_1);
} else if (const auto chat = peer->asChat()) {
const auto mask = ChatDataFlag()
| ChatDataFlag::Deactivated
| ChatDataFlag::Forbidden
| ChatDataFlag::Left
| ChatDataFlag::Creator;
return rpl::combine(
PeerFlagsValue(chat, mask),
AdminRightsValue(chat),
DefaultRestrictionsValue(chat, rights),
[rights](
ChatDataFlags flags, ChatDataFlags flags,
Data::Flags<ChatAdminRights>::Change adminRights, Data::Flags<ChatAdminRights>::Change adminRights,
bool defaultSendMessagesRestriction) { ChatRestrictions defaultSendRestrictions) {
const auto amOutFlags = 0 const auto amOutFlags = ChatDataFlag()
| ChatDataFlag::Deactivated | ChatDataFlag::Deactivated
| ChatDataFlag::Forbidden | ChatDataFlag::Forbidden
| ChatDataFlag::Left; | ChatDataFlag::Left;
return !(flags & amOutFlags) return !(flags & amOutFlags)
&& ((flags & ChatDataFlag::Creator) && ((flags & ChatDataFlag::Creator)
|| (adminRights.value != ChatAdminRights(0)) || (adminRights.value != ChatAdminRights(0))
|| !defaultSendMessagesRestriction); || (rights & ~defaultSendRestrictions));
}); });
} } else if (const auto channel = peer->asChannel()) {
using Flag = ChannelDataFlag;
rpl::producer<bool> CanWriteValue(ChannelData *channel, bool checkForForum) { const auto mask = Flag()
using Flag = ChannelDataFlag; | Flag::Left
const auto mask = 0 | Flag::Forum
| Flag::Left | Flag::JoinToWrite
| Flag::Forum | Flag::HasLink
| Flag::JoinToWrite | Flag::Forbidden
| Flag::HasLink | Flag::Creator
| Flag::Forbidden | Flag::Broadcast;
| Flag::Creator return rpl::combine(
| Flag::Broadcast; PeerFlagsValue(channel, mask),
return rpl::combine( AdminRightValue(
PeerFlagsValue(channel, mask), channel,
AdminRightValue( ChatAdminRight::PostMessages),
channel, RestrictionsValue(channel, rights),
ChatAdminRight::PostMessages), DefaultRestrictionsValue(channel, rights),
RestrictionValue( [=](
channel, ChannelDataFlags flags,
ChatRestriction::SendMessages), bool postMessagesRight,
DefaultRestrictionValue( ChatRestrictions sendRestriction,
channel, ChatRestrictions defaultSendRestriction) {
ChatRestriction::SendMessages), const auto notAmInFlags = Flag::Left | Flag::Forbidden;
[=]( const auto forumRestriction = forbidInForums
ChannelDataFlags flags, && (flags & Flag::Forum);
bool postMessagesRight, const auto allowed = !(flags & notAmInFlags)
bool sendMessagesRestriction, || ((flags & Flag::HasLink)
bool defaultSendMessagesRestriction) { && !(flags & Flag::JoinToWrite));
const auto notAmInFlags = Flag::Left | Flag::Forbidden; const auto restricted = sendRestriction
const auto forumRestriction = checkForForum | defaultSendRestriction;
&& (flags & Flag::Forum); return allowed
const auto allowed = !(flags & notAmInFlags) && !forumRestriction
|| ((flags & Flag::HasLink) && !(flags & Flag::JoinToWrite)); && (postMessagesRight
return allowed || (flags & Flag::Creator)
&& !forumRestriction || (!(flags & Flag::Broadcast)
&& (postMessagesRight && (rights & ~restricted)));
|| (flags & Flag::Creator) });
|| (!(flags & Flag::Broadcast)
&& !sendMessagesRestriction
&& !defaultSendMessagesRestriction));
});
}
rpl::producer<bool> CanWriteValue(
not_null<PeerData*> peer,
bool checkForForum) {
if (auto user = peer->asUser()) {
return CanWriteValue(user);
} else if (auto chat = peer->asChat()) {
return CanWriteValue(chat);
} else if (auto channel = peer->asChannel()) {
return CanWriteValue(channel, checkForForum);
} }
Unexpected("Bad peer value in CanWriteValue"); Unexpected("Peer type in Data::CanSendAnyOfValue.");
}
rpl::producer<bool> CanWriteValue(not_null<ForumTopic*> topic) {
using Flag = ChannelDataFlag;
const auto mask = 0
| Flag::Left
| Flag::JoinToWrite
| Flag::Forum
| Flag::Forbidden;
const auto channel = topic->channel();
return rpl::combine(
PeerFlagsValue(channel.get(), mask),
RestrictionValue(
channel,
ChatRestriction::SendMessages),
DefaultRestrictionValue(
channel,
ChatRestriction::SendMessages),
topic->session().changes().topicFlagsValue(
topic,
TopicUpdate::Flag::Closed),
[=](
ChannelDataFlags flags,
bool sendMessagesRestriction,
bool defaultSendMessagesRestriction,
auto) {
const auto notAmInFlags = Flag::Left | Flag::Forbidden;
const auto allowed = !(flags & notAmInFlags);
return allowed
&& !sendMessagesRestriction
&& !defaultSendMessagesRestriction
&& (!topic->closed() || topic->canToggleClosed());
});
} }
// This is duplicated in PeerData::canPinMessages(). // This is duplicated in PeerData::canPinMessages().

View file

@ -7,10 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include <rpl/filter.h>
#include <rpl/map.h>
#include <rpl/combine.h>
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_chat_participant_status.h"
enum class ImageRoundRadius; enum class ImageRoundRadius;
@ -53,6 +51,7 @@ template <
typename ChangeType = typename PeerType::Flags::Change> typename ChangeType = typename PeerType::Flags::Change>
inline auto PeerFlagsValue(PeerType *peer) { inline auto PeerFlagsValue(PeerType *peer) {
Expects(peer != nullptr); Expects(peer != nullptr);
return peer->flagsValue(); return peer->flagsValue();
} }
@ -101,15 +100,48 @@ inline auto PeerFullFlagValue(
return SingleFlagValue(PeerFullFlagsValue(peer), flag); return SingleFlagValue(PeerFullFlagsValue(peer), flag);
} }
[[nodiscard]] rpl::producer<bool> CanWriteValue(UserData *user); [[nodiscard]] rpl::producer<bool> CanSendAnyOfValue(
[[nodiscard]] rpl::producer<bool> CanWriteValue(ChatData *chat); not_null<Data::Thread*> thread,
[[nodiscard]] rpl::producer<bool> CanWriteValue( ChatRestrictions rights,
ChannelData *channel, bool forbidInForums = true);
bool checkForForum = true); [[nodiscard]] rpl::producer<bool> CanSendAnyOfValue(
[[nodiscard]] rpl::producer<bool> CanWriteValue(
not_null<PeerData*> peer, not_null<PeerData*> peer,
bool checkForForum = true); ChatRestrictions rights,
[[nodiscard]] rpl::producer<bool> CanWriteValue(not_null<ForumTopic*> topic); bool forbidInForums = true);
[[nodiscard]] inline rpl::producer<bool> CanSendValue(
not_null<Thread*> thread,
ChatRestriction right,
bool forbidInForums = true) {
return CanSendAnyOfValue(thread, right, forbidInForums);
}
[[nodiscard]] inline rpl::producer<bool> CanSendValue(
not_null<PeerData*> peer,
ChatRestriction right,
bool forbidInForums = true) {
return CanSendAnyOfValue(peer, right, forbidInForums);
}
[[nodiscard]] inline rpl::producer<bool> CanSendTextsValue(
not_null<Thread*> thread,
bool forbidInForums = true) {
return CanSendValue(thread, ChatRestriction::SendOther, forbidInForums);
}
[[nodiscard]] inline rpl::producer<bool> CanSendTextsValue(
not_null<PeerData*> peer,
bool forbidInForums = true) {
return CanSendValue(peer, ChatRestriction::SendOther, forbidInForums);
}
[[nodiscard]] inline rpl::producer<bool> CanSendAnythingValue(
not_null<Thread*> thread,
bool forbidInForums = true) {
return CanSendAnyOfValue(thread, AllSendRestrictions(), forbidInForums);
}
[[nodiscard]] inline rpl::producer<bool> CanSendAnythingValue(
not_null<PeerData*> peer,
bool forbidInForums = true) {
return CanSendAnyOfValue(peer, AllSendRestrictions(), forbidInForums);
}
[[nodiscard]] rpl::producer<bool> CanPinMessagesValue( [[nodiscard]] rpl::producer<bool> CanPinMessagesValue(
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<bool> CanManageGroupCallValue( [[nodiscard]] rpl::producer<bool> CanManageGroupCallValue(

View file

@ -392,7 +392,7 @@ Data::MessagesSlice ScheduledMessages::list(not_null<History*> history) {
void ScheduledMessages::request(not_null<History*> history) { void ScheduledMessages::request(not_null<History*> history) {
const auto peer = history->peer; const auto peer = history->peer;
if (peer->isBroadcast() && !peer->canWrite()) { if (peer->isBroadcast() && !Data::CanSendAnything(peer)) {
return; return;
} }
auto &request = _requests[history]; auto &request = _requests[history];

View file

@ -44,11 +44,6 @@ const PeerNotifySettings &Thread::notify() const {
return const_cast<Thread*>(this)->notify(); return const_cast<Thread*>(this)->notify();
} }
bool Thread::canWrite() const {
const auto topic = asTopic();
return topic ? topic->canWrite() : peer()->canWrite();
}
void Thread::setUnreadThingsKnown() { void Thread::setUnreadThingsKnown() {
_flags |= Flag::UnreadThingsKnown; _flags |= Flag::UnreadThingsKnown;
} }

View file

@ -7,12 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/flags.h"
#include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_message_view.h" #include "dialogs/ui/dialogs_message_view.h"
#include "ui/text/text.h" #include "ui/text/text.h"
#include <deque> #include <deque>
enum class ChatRestriction;
using ChatRestrictions = base::flags<ChatRestriction>;
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -67,7 +71,6 @@ public:
[[nodiscard]] PeerNotifySettings &notify(); [[nodiscard]] PeerNotifySettings &notify();
[[nodiscard]] const PeerNotifySettings &notify() const; [[nodiscard]] const PeerNotifySettings &notify() const;
[[nodiscard]] bool canWrite() const;
void setUnreadThingsKnown(); void setUnreadThingsKnown();
[[nodiscard]] HistoryUnreadThings::Proxy unreadMentions(); [[nodiscard]] HistoryUnreadThings::Proxy unreadMentions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const; [[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const;

View file

@ -293,11 +293,6 @@ bool UserData::isInaccessible() const {
return flags() & UserDataFlag::Deleted; return flags() & UserDataFlag::Deleted;
} }
bool UserData::canWrite() const {
// Duplicated in Data::CanWriteValue().
return !isInaccessible() && !isRepliesChat();
}
bool UserData::applyMinPhoto() const { bool UserData::applyMinPhoto() const {
return !(flags() & UserDataFlag::DiscardMinPhoto); return !(flags() & UserDataFlag::DiscardMinPhoto);
} }
@ -314,10 +309,6 @@ bool UserData::canReceiveGifts() const {
return flags() & UserDataFlag::CanReceiveGifts; return flags() & UserDataFlag::CanReceiveGifts;
} }
bool UserData::canReceiveVoices() const {
return !(flags() & UserDataFlag::VoiceMessagesForbidden);
}
bool UserData::canShareThisContactFast() const { bool UserData::canShareThisContactFast() const {
return !_phone.isEmpty(); return !_phone.isEmpty();
} }

View file

@ -112,7 +112,6 @@ public:
[[nodiscard]] bool isBot() const; [[nodiscard]] bool isBot() const;
[[nodiscard]] bool isSupport() const; [[nodiscard]] bool isSupport() const;
[[nodiscard]] bool isInaccessible() const; [[nodiscard]] bool isInaccessible() const;
[[nodiscard]] bool canWrite() const;
[[nodiscard]] bool applyMinPhoto() const; [[nodiscard]] bool applyMinPhoto() const;
[[nodiscard]] bool hasPersonalPhoto() const; [[nodiscard]] bool hasPersonalPhoto() const;
@ -120,7 +119,6 @@ public:
[[nodiscard]] bool canAddContact() const; [[nodiscard]] bool canAddContact() const;
[[nodiscard]] bool canReceiveGifts() const; [[nodiscard]] bool canReceiveGifts() const;
[[nodiscard]] bool canReceiveVoices() const;
// In Data::Session::processUsers() we check only that. // In Data::Session::processUsers() we check only that.
// When actually trying to share contact we perform // When actually trying to share contact we perform

View file

@ -255,8 +255,17 @@ QString GeneratePermissionsChangeText(
static auto phraseMap = std::map<Flags, tr::phrase<>>{ static auto phraseMap = std::map<Flags, tr::phrase<>>{
{ Flag::ViewMessages, tr::lng_admin_log_banned_view_messages }, { Flag::ViewMessages, tr::lng_admin_log_banned_view_messages },
{ Flag::SendMessages, tr::lng_admin_log_banned_send_messages }, { Flag::SendOther, tr::lng_admin_log_banned_send_messages },
{ Flag::SendMedia, tr::lng_admin_log_banned_send_media }, { Flag::SendPhotos, tr::lng_admin_log_banned_send_photos },
{ Flag::SendVideos, tr::lng_admin_log_banned_send_videos },
{ Flag::SendMusic, tr::lng_admin_log_banned_send_music },
{ Flag::SendFiles, tr::lng_admin_log_banned_send_files },
{
Flag::SendVoiceMessages,
tr::lng_admin_log_banned_send_voice_messages },
{
Flag::SendVideoMessages,
tr::lng_admin_log_banned_send_video_messages },
{ Flag::SendStickers { Flag::SendStickers
| Flag::SendGifs | Flag::SendGifs
| Flag::SendInline | Flag::SendInline

View file

@ -947,13 +947,13 @@ not_null<HistoryItem*> History::addNewToBack(
if (peer->isChat()) { if (peer->isChat()) {
botNotInChat = item->from()->isUser() botNotInChat = item->from()->isUser()
&& (!peer->asChat()->participants.empty() && (!peer->asChat()->participants.empty()
|| !peer->canWrite()) || !Data::CanSendAnything(peer))
&& !peer->asChat()->participants.contains( && !peer->asChat()->participants.contains(
item->from()->asUser()); item->from()->asUser());
} else if (peer->isMegagroup()) { } else if (peer->isMegagroup()) {
botNotInChat = item->from()->isUser() botNotInChat = item->from()->isUser()
&& (peer->asChannel()->mgInfo->botStatus != 0 && (peer->asChannel()->mgInfo->botStatus != 0
|| !peer->canWrite()) || !Data::CanSendAnything(peer))
&& !peer->asChannel()->mgInfo->bots.contains( && !peer->asChannel()->mgInfo->bots.contains(
item->from()->asUser()); item->from()->asUser());
} }
@ -1501,9 +1501,15 @@ void History::addItemsToLists(
if (!lastKeyboardInited) { if (!lastKeyboardInited) {
bool botNotInChat = false; bool botNotInChat = false;
if (peer->isChat()) { if (peer->isChat()) {
botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.empty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser()); botNotInChat = (!Data::CanSendAnything(peer)
|| !peer->asChat()->participants.empty())
&& item->author()->isUser()
&& !peer->asChat()->participants.contains(item->author()->asUser());
} else if (peer->isMegagroup()) { } else if (peer->isMegagroup()) {
botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser()); botNotInChat = (!Data::CanSendAnything(peer)
|| peer->asChannel()->mgInfo->botStatus != 0)
&& item->author()->isUser()
&& !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser());
} }
if (wasKeyboardHide || botNotInChat) { if (wasKeyboardHide || botNotInChat) {
clearLastKeyboard(); clearLastKeyboard();

View file

@ -2070,7 +2070,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto canReply = [&] { const auto canReply = [&] {
const auto peer = item->history()->peer; const auto peer = item->history()->peer;
const auto topic = item->topic(); const auto topic = item->topic();
return topic ? topic->canWrite() : peer->canWrite(); return topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer);
}(); }();
if (canReply) { if (canReply) {
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {

View file

@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_game.h" #include "data/data_game.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_group_call.h" // Data::GroupCall::id(). #include "data/data_group_call.h" // Data::GroupCall::id().
#include "data/data_poll.h" // PollData::publicVotes.
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
@ -1836,9 +1837,9 @@ bool HistoryItem::canBeEdited() const {
if (isPost()) { if (isPost()) {
return channel->canPublish(); return channel->canPublish();
} else if (const auto topic = this->topic()) { } else if (const auto topic = this->topic()) {
return topic->canWrite(); return Data::CanSendAnything(topic);
} else { } else {
return channel->canWrite(); return Data::CanSendAnything(channel);
} }
} else { } else {
return false; return false;
@ -1958,6 +1959,53 @@ bool HistoryItem::suggestDeleteAllReport() const {
return !isPost() && !out(); return !isPost() && !out();
} }
ChatRestriction HistoryItem::requiredSendRight() const {
const auto media = this->media();
if (media && media->game()) {
return ChatRestriction::SendGames;
}
const auto photo = (media && !media->webpage())
? media->photo()
: nullptr;
const auto document = (media && !media->webpage())
? media->document()
: nullptr;
if (photo) {
return ChatRestriction::SendPhotos;
} else if (document) {
return document->requiredSendRight();
} else if (media && media->poll()) {
return ChatRestriction::SendPolls;
}
return ChatRestriction::SendOther;
}
bool HistoryItem::requiresSendInlineRight() const {
return Has<HistoryMessageVia>();
}
std::optional<QString> HistoryItem::errorTextForForward(
not_null<Data::Thread*> to) const {
const auto requiredRight = requiredSendRight();
const auto requiresInline = requiresSendInlineRight();
const auto peer = to->peer();
constexpr auto kInline = ChatRestriction::SendInline;
if (const auto error = Data::RestrictionError(peer, requiredRight)) {
return *error;
} else if (requiresInline && !Data::CanSend(to, kInline)) {
return Data::RestrictionError(peer, kInline).value_or(
tr::lng_forward_cant(tr::now));
} else if (_media
&& _media->poll()
&& _media->poll()->publicVotes()
&& peer->isBroadcast()) {
return tr::lng_restricted_send_public_polls(tr::now);
} else if (!Data::CanSend(to, requiredRight, false)) {
return tr::lng_forward_cant(tr::now);
}
return {};
}
bool HistoryItem::canReact() const { bool HistoryItem::canReact() const {
if (!isRegular() || isService()) { if (!isRegular() || isService()) {
return false; return false;

View file

@ -393,6 +393,10 @@ public:
[[nodiscard]] bool suggestReport() const; [[nodiscard]] bool suggestReport() const;
[[nodiscard]] bool suggestBanReport() const; [[nodiscard]] bool suggestBanReport() const;
[[nodiscard]] bool suggestDeleteAllReport() const; [[nodiscard]] bool suggestDeleteAllReport() const;
[[nodiscard]] ChatRestriction requiredSendRight() const;
[[nodiscard]] bool requiresSendInlineRight() const;
[[nodiscard]] std::optional<QString> errorTextForForward(
not_null<Data::Thread*> to) const;
[[nodiscard]] bool canReact() const; [[nodiscard]] bool canReact() const;
enum class ReactionSource { enum class ReactionSource {

View file

@ -41,15 +41,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
[[nodiscard]] bool HasInlineItems(const HistoryItemsList &items) {
for (const auto &item : items) {
if (item->viaBot()) {
return true;
}
}
return false;
}
bool PeerCallKnown(not_null<PeerData*> peer) { bool PeerCallKnown(not_null<PeerData*> peer) {
if (peer->groupCall() != nullptr) { if (peer->groupCall() != nullptr) {
return true; return true;
@ -70,29 +61,28 @@ QString GetErrorTextForSending(
const auto topic = forum const auto topic = forum
? forum->topicFor(request.topicRootId) ? forum->topicFor(request.topicRootId)
: nullptr; : nullptr;
if (!(topic ? topic->canWrite() : peer->canWrite())) { const auto thread = topic
return tr::lng_forward_cant(tr::now); ? not_null<Data::Thread*>(topic)
} : peer->owner().history(peer);
if (request.forward) { if (request.forward) {
for (const auto &item : *request.forward) { for (const auto &item : *request.forward) {
if (const auto media = item->media()) { if (const auto error = item->errorTextForForward(thread)) {
const auto error = media->errorTextForForward(peer); return *error;
if (!error.isEmpty() && error != u"skip"_q) {
return error;
}
} }
} }
} }
const auto error = Data::RestrictionError( const auto hasText = (request.text && !request.text->empty());
peer, if (hasText) {
ChatRestriction::SendInline); const auto error = Data::RestrictionError(
if (error && request.forward && HasInlineItems(*request.forward)) { peer,
return *error; ChatRestriction::SendOther);
if (error) {
return *error;
} else if (!Data::CanSendTexts(thread)) {
return tr::lng_forward_cant(tr::now);
}
} }
if (peer->slowmodeApplied()) { if (peer->slowmodeApplied()) {
const auto hasText = (request.text && !request.text->empty());
const auto count = (hasText ? 1 : 0) const auto count = (hasText ? 1 : 0)
+ (request.forward ? int(request.forward->size()) : 0); + (request.forward ? int(request.forward->size()) : 0);
if (const auto history = peer->owner().historyLoaded(peer)) { if (const auto history = peer->owner().historyLoaded(peer)) {

View file

@ -25,8 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_requests_box.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/mime_type.h" #include "core/mime_type.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/choose_theme_controller.h" #include "ui/chat/choose_theme_controller.h"
@ -726,7 +724,7 @@ HistoryWidget::HistoryWidget(
const auto account = &_peer->account(); const auto account = &_peer->account();
closeCurrent(); closeCurrent();
if (const auto primary = Core::App().windowFor(account)) { if (const auto primary = Core::App().windowFor(account)) {
primary->show(Ui::MakeInformBox(unavailable)); controller->showToast({ unavailable });
} }
return; return;
} }
@ -940,19 +938,14 @@ void HistoryWidget::initVoiceRecordBar() {
if (_peer) { if (_peer) {
if (const auto error = Data::RestrictionError( if (const auto error = Data::RestrictionError(
_peer, _peer,
ChatRestriction::SendMedia)) { ChatRestriction::SendVoiceMessages)) {
return error;
}
if (const auto error = Data::RestrictionError(
_peer,
UserRestriction::SendVoiceMessages)) {
return error; return error;
} }
} }
return std::nullopt; return std::nullopt;
}(); }();
if (error) { if (error) {
controller()->show(Ui::MakeInformBox(*error)); controller()->showToast({ *error });
return true; return true;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return true; return true;
@ -1013,10 +1006,7 @@ void HistoryWidget::initVoiceRecordBar() {
_voiceRecordBar->recordingTipRequests( _voiceRecordBar->recordingTipRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
Ui::ShowMultilineToast({ controller()->showToast({ tr::lng_record_hold_tip(tr::now) });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { tr::lng_record_hold_tip(tr::now) },
});
}, lifetime()); }, lifetime());
_voiceRecordBar->hideFast(); _voiceRecordBar->hideFast();
@ -1564,10 +1554,7 @@ void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
} }
return; return;
} else if (_voiceRecordBar->isActive()) { } else if (_voiceRecordBar->isActive()) {
Ui::ShowMultilineToast({ controller()->showToast({ tr::lng_chat_theme_cant_voice(tr::now) });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { tr::lng_chat_theme_cant_voice(tr::now) },
});
return; return;
} }
_chooseTheme = std::make_unique<Ui::ChooseThemeController>( _chooseTheme = std::make_unique<Ui::ChooseThemeController>(
@ -2144,7 +2131,7 @@ void HistoryWidget::showHistory(
if (peerId) { if (peerId) {
_peer = session().data().peer(peerId); _peer = session().data().peer(peerId);
_canSendMessages = _peer->canWrite(); _canSendMessages = Data::CanSendAnything(_peer);
_contactStatus = std::make_unique<HistoryView::ContactStatus>( _contactStatus = std::make_unique<HistoryView::ContactStatus>(
controller(), controller(),
this, this,
@ -2621,8 +2608,10 @@ bool HistoryWidget::canWriteMessage() const {
} }
std::optional<QString> HistoryWidget::writeRestriction() const { std::optional<QString> HistoryWidget::writeRestriction() const {
auto result = _peer const auto allWithoutPolls = Data::AllSendRestrictions()
? Data::RestrictionError(_peer, ChatRestriction::SendMessages) & ~ChatRestriction::SendPolls;
auto result = (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
? Data::RestrictionError(_peer, ChatRestriction::SendOther)
: std::nullopt; : std::nullopt;
if (result) { if (result) {
return result; return result;
@ -2827,7 +2816,15 @@ void HistoryWidget::updateControlsVisibility() {
_botMenuButton->hide(); _botMenuButton->hide();
} }
_kbScroll->hide(); _kbScroll->hide();
_fieldBarCancel->hide(); if (_replyToId || readyToForward() || _kbReplyTo) {
if (_fieldBarCancel->isHidden()) {
_fieldBarCancel->show();
updateControlsGeometry();
update();
}
} else {
_fieldBarCancel->hide();
}
_tabbedSelectorToggle->hide(); _tabbedSelectorToggle->hide();
_botKeyboardShow->hide(); _botKeyboardShow->hide();
_botKeyboardHide->hide(); _botKeyboardHide->hide();
@ -3010,12 +3007,9 @@ void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
auto was = _peer; auto was = _peer;
closeCurrent(); closeCurrent();
if (const auto primary = Core::App().windowFor(&was->account())) { if (const auto primary = Core::App().windowFor(&was->account())) {
Ui::ShowMultilineToast({ controller()->showToast({ (was && was->isMegagroup())
.parentOverride = Window::Show(primary).toastParent(), ? tr::lng_group_not_accessible(tr::now)
.text = { (was && was->isMegagroup()) : tr::lng_channel_not_accessible(tr::now) });
? tr::lng_group_not_accessible(tr::now)
: tr::lng_channel_not_accessible(tr::now) },
});
} }
return; return;
} }
@ -3653,8 +3647,9 @@ void HistoryWidget::saveEditMsg() {
return; return;
} else if (!left.text.isEmpty()) { } else if (!left.text.isEmpty()) {
const auto remove = left.text.size(); const auto remove = left.text.size();
controller()->show(Ui::MakeInformBox( controller()->showToast({
tr::lng_edit_limit_reached(tr::now, lt_count, remove))); tr::lng_edit_limit_reached(tr::now, lt_count, remove)
});
return; return;
} }
@ -3687,14 +3682,14 @@ void HistoryWidget::saveEditMsg() {
_saveEditMsgRequestId = 0; _saveEditMsgRequestId = 0;
} }
if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) { if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} else if (error == u"MESSAGE_NOT_MODIFIED"_q) { } else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
cancelEdit(); cancelEdit();
} else if (error == u"MESSAGE_EMPTY"_q) { } else if (error == u"MESSAGE_EMPTY"_q) {
_field->selectAll(); _field->selectAll();
_field->setFocus(); _field->setFocus();
} else { } else {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} }
update(); update();
})(); })();
@ -3796,27 +3791,12 @@ void HistoryWidget::send(Api::SendOptions options) {
message.textWithTags = _field->getTextWithAppliedMarkdown(); message.textWithTags = _field->getTextWithAppliedMarkdown();
message.webPageId = webPageId; message.webPageId = webPageId;
if (_canSendMessages) { const auto ignoreSlowmodeCountdown = (options.scheduled != 0);
const auto topicRootId = _replyEditMsg if (showSendMessageError(
? _replyEditMsg->topicRootId() message.textWithTags,
: 0; ignoreSlowmodeCountdown)) {
const auto error = GetErrorTextForSending( return;
_peer,
{
.topicRootId = topicRootId,
.forward = &_forwardPanel->items(),
.text = &message.textWithTags,
.ignoreSlowmodeCountdown = (options.scheduled != 0),
});
if (!error.isEmpty()) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(controller()).toastParent(),
.text = { error },
});
return;
}
} }
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
clearFieldText(); clearFieldText();
@ -3851,6 +3831,12 @@ void HistoryWidget::sendScheduled() {
if (!_list) { if (!_list) {
return; return;
} }
const auto ignoreSlowmodeCountdown = true;
if (showSendMessageError(
_field->getTextWithAppliedMarkdown(),
ignoreSlowmodeCountdown)) {
return;
}
const auto callback = [=](Api::SendOptions options) { send(options); }; const auto callback = [=](Api::SendOptions options) { send(options); };
controller()->show( controller()->show(
HistoryView::PrepareScheduleBox(_list, sendMenuType(), callback), HistoryView::PrepareScheduleBox(_list, sendMenuType(), callback),
@ -4127,19 +4113,14 @@ void HistoryWidget::finishAnimating() {
void HistoryWidget::chooseAttach( void HistoryWidget::chooseAttach(
std::optional<bool> overrideSendImagesAsPhotos) { std::optional<bool> overrideSendImagesAsPhotos) {
if (_editMsgId) { if (_editMsgId) {
controller()->show(Ui::MakeInformBox(tr::lng_edit_caption_attach())); controller()->showToast({ tr::lng_edit_caption_attach(tr::now) });
return; return;
} }
if (!_peer || !_canSendMessages) { if (!_peer || !_canSendMessages) {
return; return;
} else if (const auto error = Data::RestrictionError( } else if (const auto error = Data::AnyFileRestrictionError(_peer)) {
_peer, controller()->showToast({ *error });
ChatRestriction::SendMedia)) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(controller()).toastParent(),
.text = { *error },
});
return; return;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return; return;
@ -4371,9 +4352,8 @@ bool HistoryWidget::readyToForward() const {
bool HistoryWidget::hasSilentToggle() const { bool HistoryWidget::hasSilentToggle() const {
return _peer return _peer
&& _peer->isChannel() && _peer->isBroadcast()
&& !_peer->isMegagroup() && Data::CanSendAnything(_peer)
&& _peer->canWrite()
&& !session().data().notifySettings().silentPostsUnknown(_peer); && !session().data().notifySettings().silentPostsUnknown(_peer);
} }
@ -4421,7 +4401,7 @@ bool HistoryWidget::isChoosingTheme() const {
bool HistoryWidget::isMuteUnmute() const { bool HistoryWidget::isMuteUnmute() const {
return _peer return _peer
&& ((_peer->isBroadcast() && !_peer->asChannel()->canPublish()) && ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
|| (_peer->isGigagroup() && !_peer->asChannel()->canWrite()) || (_peer->isGigagroup() && !Data::CanSendAnything(_peer))
|| _peer->isRepliesChat()); || _peer->isRepliesChat());
} }
@ -4725,9 +4705,15 @@ void HistoryWidget::showMembersDropdown() {
bool HistoryWidget::pushTabbedSelectorToThirdSection( bool HistoryWidget::pushTabbedSelectorToThirdSection(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
const Window::SectionShow &params) { const Window::SectionShow &params) {
const auto selectorTypes = ChatRestriction::SendOther
| ChatRestriction::SendInline
| ChatRestriction::SendStickers
| ChatRestriction::SendGifs;
if (!_tabbedPanel) { if (!_tabbedPanel) {
return true; return true;
} else if (!thread->canWrite()) { } else if (!Data::CanSendAnyOf(
thread,
Data::TabbedPanelSendRestrictions())) {
Core::App().settings().setTabbedReplacedWithInfo(true); Core::App().settings().setTabbedReplacedWithInfo(true);
controller()->showPeerInfo(thread, params.withThirdColumn()); controller()->showPeerInfo(thread, params.withThirdColumn());
return false; return false;
@ -4978,19 +4964,18 @@ void HistoryWidget::updateFieldPlaceholder() {
bool HistoryWidget::showSendingFilesError( bool HistoryWidget::showSendingFilesError(
const Ui::PreparedList &list) const { const Ui::PreparedList &list) const {
return showSendingFilesError(list, std::nullopt);
}
bool HistoryWidget::showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const {
const auto text = [&] { const auto text = [&] {
const auto error = _peer const auto error = _peer
? Data::RestrictionError( ? Data::FileRestrictionError(_peer, list, compress)
_peer,
ChatRestriction::SendMedia)
: std::nullopt; : std::nullopt;
if (error) { if (error) {
return *error; return *error;
} else if (!canWriteMessage()) {
return tr::lng_forward_send_files_cant(tr::now);
}
if (_peer->slowmodeApplied() && !list.canBeSentInSlowmode()) {
return tr::lng_slowmode_no_many(tr::now);
} else if (const auto left = _peer->slowmodeSecondsLeft()) { } else if (const auto left = _peer->slowmodeSecondsLeft()) {
return tr::lng_slowmode_enabled( return tr::lng_slowmode_enabled(
tr::now, tr::now,
@ -5017,11 +5002,31 @@ bool HistoryWidget::showSendingFilesError(
controller()->show(Box(FileSizeLimitBox, &session(), fileSize)); controller()->show(Box(FileSizeLimitBox, &session(), fileSize));
return true; return true;
} }
controller()->showToast({ text });
return true;
}
Ui::ShowMultilineToast({ bool HistoryWidget::showSendMessageError(
.parentOverride = Window::Show(controller()).toastParent(), const TextWithTags &textWithTags,
.text = { text }, bool ignoreSlowmodeCountdown) const {
}); if (!_canSendMessages) {
return false;
}
const auto topicRootId = _replyEditMsg
? _replyEditMsg->topicRootId()
: 0;
const auto error = GetErrorTextForSending(
_peer,
{
.topicRootId = topicRootId,
.forward = &_forwardPanel->items(),
.text = &textWithTags,
.ignoreSlowmodeCountdown = ignoreSlowmodeCountdown,
});
if (error.isEmpty()) {
return false;
}
controller()->showToast({ error });
return true; return true;
} }
@ -5045,11 +5050,10 @@ bool HistoryWidget::confirmSendingFiles(
bool HistoryWidget::confirmSendingFiles( bool HistoryWidget::confirmSendingFiles(
Ui::PreparedList &&list, Ui::PreparedList &&list,
const QString &insertTextOnCancel) { const QString &insertTextOnCancel) {
if (showSendingFilesError(list)) {
return false;
}
if (_editMsgId) { if (_editMsgId) {
controller()->show(Ui::MakeInformBox(tr::lng_edit_caption_attach())); controller()->showToast({ tr::lng_edit_caption_attach(tr::now) });
return false;
} else if (showSendingFilesError(list)) {
return false; return false;
} }
@ -5061,7 +5065,8 @@ bool HistoryWidget::confirmSendingFiles(
controller(), controller(),
std::move(list), std::move(list),
text, text,
_peer, DefaultLimitsForPeer(_peer),
DefaultCheckForPeer(controller(), _peer),
Api::SendType::Normal, Api::SendType::Normal,
sendMenuType()); sendMenuType());
_field->setTextWithTags({}); _field->setTextWithTags({});
@ -5106,16 +5111,15 @@ void HistoryWidget::sendingFilesConfirmed(
bool ctrlShiftEnter) { bool ctrlShiftEnter) {
Expects(list.filesToProcess.empty()); Expects(list.filesToProcess.empty());
if (showSendingFilesError(list)) { const auto compress = way.sendImagesAsPhotos();
if (showSendingFilesError(list, compress)) {
return; return;
} }
auto groups = DivideByGroups( auto groups = DivideByGroups(
std::move(list), std::move(list),
way, way,
_peer->slowmodeApplied()); _peer->slowmodeApplied());
const auto type = way.sendImagesAsPhotos() const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
? SendMediaType::Photo
: SendMediaType::File;
auto action = prepareSendAction(options); auto action = prepareSendAction(options);
action.clearDraft = false; action.clearDraft = false;
if ((groups.size() != 1 || !groups.front().sentWithCaption()) if ((groups.size() != 1 || !groups.front().sentWithCaption())
@ -5412,10 +5416,7 @@ int HistoryWidget::countInitialScrollTop() {
const auto itemTop = _list->itemTop(item); const auto itemTop = _list->itemTop(item);
if (itemTop < 0) { if (itemTop < 0) {
setMsgId(0); setMsgId(0);
Ui::ShowMultilineToast({ controller()->showToast({ tr::lng_message_not_found(tr::now) });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { tr::lng_message_not_found(tr::now) },
});
return countInitialScrollTop(); return countInitialScrollTop();
} else { } else {
const auto view = item->mainView(); const auto view = item->mainView();
@ -6112,10 +6113,7 @@ bool HistoryWidget::showSlowmodeError() {
if (text.isEmpty()) { if (text.isEmpty()) {
return false; return false;
} }
Ui::ShowMultilineToast({ controller()->showToast({ text });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { text },
});
return true; return true;
} }
@ -6136,7 +6134,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
auto errorText = result.result->getErrorOnSend(_history); auto errorText = result.result->getErrorOnSend(_history);
if (!errorText.isEmpty()) { if (!errorText.isEmpty()) {
controller()->show(Ui::MakeInformBox(errorText)); controller()->showToast({ errorText });
return; return;
} }
@ -6602,9 +6600,7 @@ bool HistoryWidget::sendExistingDocument(
? Data::RestrictionError(_peer, ChatRestriction::SendStickers) ? Data::RestrictionError(_peer, ChatRestriction::SendStickers)
: std::nullopt; : std::nullopt;
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} else if (!_peer } else if (!_peer
|| !_canSendMessages || !_canSendMessages
@ -6636,12 +6632,10 @@ bool HistoryWidget::sendExistingPhoto(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Api::SendOptions options) { Api::SendOptions options) {
const auto error = _peer const auto error = _peer
? Data::RestrictionError(_peer, ChatRestriction::SendMedia) ? Data::RestrictionError(_peer, ChatRestriction::SendPhotos)
: std::nullopt; : std::nullopt;
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} else if (!_peer || !_canSendMessages) { } else if (!_peer || !_canSendMessages) {
return false; return false;
@ -6754,7 +6748,7 @@ void HistoryWidget::processReply() {
return; return;
} else if (_processingReplyItem->history() == _migrated) { } else if (_processingReplyItem->history() == _migrated) {
if (_processingReplyItem->isService()) { if (_processingReplyItem->isService()) {
controller()->show(Ui::MakeInformBox(tr::lng_reply_cant())); controller()->showToast({ tr::lng_reply_cant(tr::now) });
} else { } else {
const auto itemId = _processingReplyItem->fullId(); const auto itemId = _processingReplyItem->fullId();
controller()->show( controller()->show(
@ -6777,13 +6771,13 @@ void HistoryWidget::processReply() {
if (forum->topicDeleted(topicRootId)) { if (forum->topicDeleted(topicRootId)) {
return processCancel(); return processCancel();
} else if (const auto topic = forum->topicFor(topicRootId)) { } else if (const auto topic = forum->topicFor(topicRootId)) {
if (!topic->canWrite()) { if (!Data::CanSendAnything(topic)) {
return processCancel(); return processCancel();
} }
} else { } else {
forum->requestTopic(topicRootId, processContinue()); forum->requestTopic(topicRootId, processContinue());
} }
} else if (!_peer->canWrite()) { } else if (!Data::CanSendAnything(_peer)) {
return processCancel(); return processCancel();
} }
setReplyFieldsFromProcessing(); setReplyFieldsFromProcessing();
@ -6849,8 +6843,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
} else if (_chooseTheme) { } else if (_chooseTheme) {
toggleChooseChatTheme(_peer); toggleChooseChatTheme(_peer);
} else if (_voiceRecordBar->isActive()) { } else if (_voiceRecordBar->isActive()) {
controller()->show( controller()->showToast({ tr::lng_edit_caption_voice(tr::now) });
Ui::MakeInformBox(tr::lng_edit_caption_voice()));
return; return;
} else if (_composeSearch) { } else if (_composeSearch) {
_composeSearch->hideAnimated(); _composeSearch->hideAnimated();
@ -7264,9 +7257,11 @@ void HistoryWidget::handlePeerUpdate() {
bool HistoryWidget::updateCanSendMessage() { bool HistoryWidget::updateCanSendMessage() {
const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0; const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0;
const auto topic = replyTo ? replyTo->topic() : nullptr; const auto topic = replyTo ? replyTo->topic() : nullptr;
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto newCanSendMessages = topic const auto newCanSendMessages = topic
? topic->canWrite() ? Data::CanSendAnyOf(topic, allWithoutPolls)
: _peer->canWrite(); : Data::CanSendAnyOf(_peer, allWithoutPolls);
if (_canSendMessages == newCanSendMessages) { if (_canSendMessages == newCanSendMessages) {
return false; return false;
} }
@ -7740,9 +7735,17 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
const auto clip = e->rect(); const auto clip = e->rect();
if (_list) { if (_list) {
if (!_field->isHidden() || isRecording()) { const auto restrictionHidden = !_field->isHidden() || isRecording();
if (restrictionHidden
|| replyToId()
|| readyToForward()
|| _kbShown) {
drawField(p, clip); drawField(p, clip);
} else if (const auto error = writeRestriction()) { }
const auto error = restrictionHidden
? std::nullopt
: writeRestriction();
if (error) {
drawRestrictedWrite(p, *error); drawRestrictedWrite(p, *error);
} }
} else { } else {

View file

@ -430,6 +430,12 @@ private:
Ui::PreparedList &&list, Ui::PreparedList &&list,
const QString &insertTextOnCancel = QString()); const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Ui::PreparedList &list) const; bool showSendingFilesError(const Ui::PreparedList &list) const;
bool showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const;
bool showSendMessageError(
const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown) const;
void sendingFilesConfirmed( void sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,

View file

@ -354,6 +354,7 @@ public:
[[nodiscard]] bool isDisplayed() const; [[nodiscard]] bool isDisplayed() const;
[[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] bool readyToForward() const; [[nodiscard]] bool readyToForward() const;
[[nodiscard]] const HistoryItemsList &forwardItems() const;
[[nodiscard]] FullMsgId replyingToMessage() const; [[nodiscard]] FullMsgId replyingToMessage() const;
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const; [[nodiscard]] rpl::producer<FullMsgId> editMsgId() const;
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const; [[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
@ -839,6 +840,10 @@ bool FieldHeader::readyToForward() const {
return !_forwardPanel->empty(); return !_forwardPanel->empty();
} }
const HistoryItemsList &FieldHeader::forwardItems() const {
return _forwardPanel->items();
}
FullMsgId FieldHeader::replyingToMessage() const { FullMsgId FieldHeader::replyingToMessage() const {
return _replyToId.current(); return _replyToId.current();
} }
@ -1057,6 +1062,10 @@ int ComposeControls::heightCurrent() const {
: _wrap->height(); : _wrap->height();
} }
const HistoryItemsList &ComposeControls::forwardItems() const {
return _header->forwardItems();
}
bool ComposeControls::focus() { bool ComposeControls::focus() {
if (isRecording()) { if (isRecording()) {
return false; return false;
@ -2061,12 +2070,12 @@ void ComposeControls::initSendButton() {
void ComposeControls::initSendAsButton(not_null<PeerData*> peer) { void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
using namespace rpl::mappers; using namespace rpl::mappers;
// SendAsPeers::shouldChoose checks PeerData::canWrite(false). // SendAsPeers::shouldChoose checks Data::CanSendAnything(PeerData*).
rpl::combine( rpl::combine(
rpl::single(peer) | rpl::then( rpl::single(peer) | rpl::then(
session().sendAsPeers().updated() | rpl::filter(_1 == peer) session().sendAsPeers().updated() | rpl::filter(_1 == peer)
), ),
Data::CanWriteValue(peer, false) Data::CanSendAnythingValue(peer, false)
) | rpl::skip(1) | rpl::start_with_next([=] { ) | rpl::skip(1) | rpl::start_with_next([=] {
if (updateSendAsButton()) { if (updateSendAsButton()) {
updateControlsVisibility(); updateControlsVisibility();
@ -2147,12 +2156,7 @@ void ComposeControls::initVoiceRecordBar() {
if (!peer) { if (!peer) {
if (const auto error = Data::RestrictionError( if (const auto error = Data::RestrictionError(
peer, peer,
ChatRestriction::SendMedia)) { ChatRestriction::SendVoiceMessages)) {
return error;
}
if (const auto error = Data::RestrictionError(
peer,
UserRestriction::SendVoiceMessages)) {
return error; return error;
} }
} }
@ -2774,9 +2778,8 @@ bool ComposeControls::hasSilentBroadcastToggle() const {
} }
const auto &peer = _history->peer; const auto &peer = _history->peer;
return peer return peer
&& peer->isChannel() && peer->isBroadcast()
&& !peer->isMegagroup() && Data::CanSendAnything(peer)
&& peer->canWrite()
&& !session().data().notifySettings().silentPostsUnknown(peer); && !session().data().notifySettings().silentPostsUnknown(peer);
} }

View file

@ -150,6 +150,7 @@ public:
[[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] bool readyToForward() const; [[nodiscard]] bool readyToForward() const;
[[nodiscard]] const HistoryItemsList &forwardItems() const;
[[nodiscard]] FullMsgId replyingToMessage() const; [[nodiscard]] FullMsgId replyingToMessage() const;
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const; [[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;

View file

@ -588,7 +588,9 @@ bool AddReplyToMessageAction(
const auto peer = item ? item->history()->peer.get() : nullptr; const auto peer = item ? item->history()->peer.get() : nullptr;
if (!item if (!item
|| !item->isRegular() || !item->isRegular()
|| (topic ? !topic->canWrite() : !peer->canWrite()) || !(topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer))
|| (context != Context::History && context != Context::Replies)) { || (context != Context::History && context != Context::Replies)) {
return false; return false;
} }

View file

@ -2955,16 +2955,18 @@ bool Message::hasFastReply() const {
} }
bool Message::displayFastReply() const { bool Message::displayFastReply() const {
const auto canWrite = [&] { const auto canSendAnything = [&] {
const auto item = data(); const auto item = data();
const auto peer = item->history()->peer; const auto peer = item->history()->peer;
const auto topic = item->topic(); const auto topic = item->topic();
return topic ? topic->canWrite() : peer->canWrite(); return topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer);
}; };
return hasFastReply() return hasFastReply()
&& data()->isRegular() && data()->isRegular()
&& canWrite() && canSendAnything()
&& !delegate()->elementInSelectionMode(); && !delegate()->elementInSelectionMode();
} }

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "menu/menu_send.h" // SendMenu::Type. #include "menu/menu_send.h" // SendMenu::Type.
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
@ -34,12 +35,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/item_text_options.h" #include "ui/item_text_options.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/effects/message_sending_animation_controller.h" #include "ui/effects/message_sending_animation_controller.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/toasts/common_toasts.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "api/api_bot.h" #include "api/api_bot.h"
#include "api/api_common.h" #include "api/api_common.h"
@ -687,18 +686,23 @@ void RepliesWidget::setupComposeControls() {
session().changes().peerFlagsValue( session().changes().peerFlagsValue(
_history->peer, _history->peer,
Data::PeerUpdate::Flag::Rights), Data::PeerUpdate::Flag::Rights),
Data::CanWriteValue(_history->peer), Data::CanSendAnythingValue(_history->peer),
std::move(topicWriteRestrictions) std::move(topicWriteRestrictions)
) | rpl::map([=](auto, auto, std::optional<QString> topicRestriction) { ) | rpl::map([=](auto, auto, std::optional<QString> topicRestriction) {
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto canSendAnything = _topic
? Data::CanSendAnyOf(_topic, allWithoutPolls)
: Data::CanSendAnyOf(_history->peer, allWithoutPolls);
const auto restriction = Data::RestrictionError( const auto restriction = Data::RestrictionError(
_history->peer, _history->peer,
ChatRestriction::SendMessages); ChatRestriction::SendOther);
return restriction return !canSendAnything
? restriction ? (restriction
? restriction
: tr::lng_group_not_accessible(tr::now))
: topicRestriction : topicRestriction
? std::move(topicRestriction) ? std::move(topicRestriction)
: !(_topic ? _topic->canWrite() : _history->peer->canWrite())
? tr::lng_group_not_accessible(tr::now)
: std::optional<QString>(); : std::optional<QString>();
}); });
@ -841,7 +845,7 @@ void RepliesWidget::setupComposeControls() {
channel->updateFull(); channel->updateFull();
if (!channel->isBroadcast()) { if (!channel->isBroadcast()) {
rpl::combine( rpl::combine(
Data::CanWriteValue(channel), Data::CanSendAnythingValue(channel),
channel->flagsValue() channel->flagsValue()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refreshJoinGroupButton(); refreshJoinGroupButton();
@ -855,13 +859,8 @@ void RepliesWidget::setupComposeControls() {
void RepliesWidget::chooseAttach( void RepliesWidget::chooseAttach(
std::optional<bool> overrideSendImagesAsPhotos) { std::optional<bool> overrideSendImagesAsPhotos) {
_choosingAttach = false; _choosingAttach = false;
if (const auto error = Data::RestrictionError( if (const auto error = Data::AnyFileRestrictionError(_history->peer)) {
_history->peer, controller()->showToast({ *error });
ChatRestriction::SendMedia)) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(controller()).toastParent(),
.text = { *error },
});
return; return;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return; return;
@ -945,7 +944,8 @@ bool RepliesWidget::confirmSendingFiles(
controller(), controller(),
std::move(list), std::move(list),
_composeControls->getTextWithAppliedMarkdown(), _composeControls->getTextWithAppliedMarkdown(),
_history->peer, DefaultLimitsForPeer(_history->peer),
DefaultCheckForPeer(controller(), _history->peer),
Api::SendType::Normal, Api::SendType::Normal,
SendMenu::Type::SilentOnly); // #TODO replies schedule SendMenu::Type::SilentOnly); // #TODO replies schedule
@ -980,7 +980,7 @@ void RepliesWidget::sendingFilesConfirmed(
bool ctrlShiftEnter) { bool ctrlShiftEnter) {
Expects(list.filesToProcess.empty()); Expects(list.filesToProcess.empty());
if (showSendingFilesError(list)) { if (showSendingFilesError(list, way.sendImagesAsPhotos())) {
return; return;
} }
auto groups = DivideByGroups( auto groups = DivideByGroups(
@ -1051,19 +1051,10 @@ bool RepliesWidget::showSlowmodeError() {
if (text.isEmpty()) { if (text.isEmpty()) {
return false; return false;
} }
Ui::ShowMultilineToast({ controller()->showToast({ text });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { text },
});
return true; return true;
} }
std::optional<QString> RepliesWidget::writeRestriction() const {
return Data::RestrictionError(
_history->peer,
ChatRestriction::SendMessages);
}
void RepliesWidget::pushReplyReturn(not_null<HistoryItem*> item) { void RepliesWidget::pushReplyReturn(not_null<HistoryItem*> item) {
if (item->history() == _history && item->inThread(_rootId)) { if (item->history() == _history && item->inThread(_rootId)) {
_cornerButtons.pushReplyReturn(item); _cornerButtons.pushReplyReturn(item);
@ -1095,16 +1086,17 @@ void RepliesWidget::uploadFile(
bool RepliesWidget::showSendingFilesError( bool RepliesWidget::showSendingFilesError(
const Ui::PreparedList &list) const { const Ui::PreparedList &list) const {
return showSendingFilesError(list, std::nullopt);
}
bool RepliesWidget::showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const {
const auto text = [&] { const auto text = [&] {
const auto peer = _history->peer; const auto peer = _history->peer;
const auto error = Data::RestrictionError( const auto error = Data::FileRestrictionError(peer, list, compress);
peer,
ChatRestriction::SendMedia);
if (error) { if (error) {
return *error; return *error;
}
if (peer->slowmodeApplied() && !list.canBeSentInSlowmode()) {
return tr::lng_slowmode_no_many(tr::now);
} else if (const auto left = _history->peer->slowmodeSecondsLeft()) { } else if (const auto left = _history->peer->slowmodeSecondsLeft()) {
return tr::lng_slowmode_enabled( return tr::lng_slowmode_enabled(
tr::now, tr::now,
@ -1132,10 +1124,7 @@ bool RepliesWidget::showSendingFilesError(
return true; return true;
} }
Ui::ShowMultilineToast({ controller()->showToast({ text });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { text },
});
return true; return true;
} }
@ -1188,17 +1177,18 @@ void RepliesWidget::send(Api::SendOptions options) {
message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPageId = webPageId; message.webPageId = webPageId;
//const auto error = GetErrorTextForSending( const auto error = GetErrorTextForSending(
// _peer, _history->peer,
// _toForward, {
// message.textWithTags); .topicRootId = _topic ? _topic->rootId() : MsgId(0),
//if (!error.isEmpty()) { .forward = &_composeControls->forwardItems(),
// Ui::ShowMultilineToast({ .text = &message.textWithTags,
// .parentOverride = Window::Show(controller()).toastParent(), .ignoreSlowmodeCountdown = (options.scheduled != 0),
// .text = { error }, });
// }); if (!error.isEmpty()) {
// return; controller()->showToast({ error });
//} return;
}
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
@ -1242,8 +1232,9 @@ void RepliesWidget::edit(
return; return;
} else if (!left.text.isEmpty()) { } else if (!left.text.isEmpty()) {
const auto remove = left.text.size(); const auto remove = left.text.size();
controller()->show(Ui::MakeInformBox( controller()->showToast({
tr::lng_edit_limit_reached(tr::now, lt_count, remove))); tr::lng_edit_limit_reached(tr::now, lt_count, remove),
});
return; return;
} }
@ -1267,13 +1258,13 @@ void RepliesWidget::edit(
} }
if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) { if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} else if (error == u"MESSAGE_NOT_MODIFIED"_q) { } else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
_composeControls->cancelEditMessage(); _composeControls->cancelEditMessage();
} else if (error == u"MESSAGE_EMPTY"_q) { } else if (error == u"MESSAGE_EMPTY"_q) {
doSetInnerFocus(); doSetInnerFocus();
} else { } else {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} }
update(); update();
return true; return true;
@ -1311,10 +1302,10 @@ void RepliesWidget::refreshJoinGroupButton() {
} }
}; };
const auto channel = _history->peer->asChannel(); const auto channel = _history->peer->asChannel();
const auto canWrite = !channel->isForum() const auto canSend = !channel->isForum()
? channel->canWrite() ? Data::CanSendAnything(channel)
: (_topic && _topic->canWrite()); : (_topic && Data::CanSendAnything(_topic));
if (channel->amIn() || canWrite) { if (channel->amIn() || canSend) {
set(nullptr); set(nullptr);
} else { } else {
if (!_joinGroup) { if (!_joinGroup) {
@ -1354,9 +1345,7 @@ bool RepliesWidget::sendExistingDocument(
_history->peer, _history->peer,
ChatRestriction::SendStickers); ChatRestriction::SendStickers);
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} else if (showSlowmodeError() } else if (showSlowmodeError()
|| ShowSendPremiumError(controller(), document)) { || ShowSendPremiumError(controller(), document)) {
@ -1389,11 +1378,9 @@ bool RepliesWidget::sendExistingPhoto(
Api::SendOptions options) { Api::SendOptions options) {
const auto error = Data::RestrictionError( const auto error = Data::RestrictionError(
_history->peer, _history->peer,
ChatRestriction::SendMedia); ChatRestriction::SendPhotos);
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; return false;
@ -1413,7 +1400,7 @@ void RepliesWidget::sendInlineResult(
not_null<UserData*> bot) { not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(_history); const auto errorText = result->getErrorOnSend(_history);
if (!errorText.isEmpty()) { if (!errorText.isEmpty()) {
controller()->show(Ui::MakeInformBox(errorText)); controller()->showToast({ errorText });
return; return;
} }
sendInlineResult(result, bot, {}, std::nullopt); sendInlineResult(result, bot, {}, std::nullopt);

View file

@ -278,6 +278,9 @@ private:
std::optional<bool> overrideSendImagesAsPhotos, std::optional<bool> overrideSendImagesAsPhotos,
const QString &insertTextOnCancel = QString()); const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Ui::PreparedList &list) const; bool showSendingFilesError(const Ui::PreparedList &list) const;
bool showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const;
void sendingFilesConfirmed( void sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,
Ui::SendFilesWay way, Ui::SendFilesWay way,
@ -307,7 +310,6 @@ private:
void refreshJoinGroupButton(); void refreshJoinGroupButton();
[[nodiscard]] bool emptyShown() const; [[nodiscard]] bool emptyShown() const;
[[nodiscard]] bool showSlowmodeError(); [[nodiscard]] bool showSlowmodeError();
[[nodiscard]] std::optional<QString> writeRestriction() const;
const not_null<History*> _history; const not_null<History*> _history;
MsgId _rootId = 0; MsgId _rootId = 0;

View file

@ -16,18 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "menu/menu_send.h" // SendMenu::Type. #include "menu/menu_send.h" // SendMenu::Type.
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/item_text_options.h" #include "ui/item_text_options.h"
#include "ui/toast/toast.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toasts/common_toasts.h"
#include "api/api_common.h" #include "api/api_common.h"
#include "api/api_editing.h" #include "api/api_editing.h"
#include "api/api_sending.h" #include "api/api_sending.h"
@ -53,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_scheduled_messages.h" #include "data/data_scheduled_messages.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_peer_values.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_result.h"
@ -199,7 +199,30 @@ ScheduledWidget::ScheduledWidget(
ScheduledWidget::~ScheduledWidget() = default; ScheduledWidget::~ScheduledWidget() = default;
void ScheduledWidget::setupComposeControls() { void ScheduledWidget::setupComposeControls() {
_composeControls->setHistory({ .history = _history.get() }); auto writeRestriction = rpl::combine(
session().changes().peerFlagsValue(
_history->peer,
Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer)
) | rpl::map([=] {
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto canSendAnything = Data::CanSendAnyOf(
_history->peer,
allWithoutPolls);
const auto restriction = Data::RestrictionError(
_history->peer,
ChatRestriction::SendOther);
return !canSendAnything
? (restriction
? restriction
: tr::lng_group_not_accessible(tr::now))
: std::optional<QString>();
});
_composeControls->setHistory({
.history = _history.get(),
.writeRestriction = std::move(writeRestriction),
});
_composeControls->height( _composeControls->height(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -308,13 +331,8 @@ void ScheduledWidget::setupComposeControls() {
} }
void ScheduledWidget::chooseAttach() { void ScheduledWidget::chooseAttach() {
if (const auto error = Data::RestrictionError( if (const auto error = Data::AnyFileRestrictionError(_history->peer)) {
_history->peer, controller()->showToast({ *error });
ChatRestriction::SendMedia)) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(controller()).toastParent(),
.text = { *error },
});
return; return;
} }
@ -392,10 +410,11 @@ bool ScheduledWidget::confirmSendingFiles(
controller(), controller(),
std::move(list), std::move(list),
_composeControls->getTextWithAppliedMarkdown(), _composeControls->getTextWithAppliedMarkdown(),
_history->peer, DefaultLimitsForPeer(_history->peer),
CanScheduleUntilOnline(_history->peer) DefaultCheckForPeer(controller(), _history->peer),
(CanScheduleUntilOnline(_history->peer)
? Api::SendType::ScheduledToUser ? Api::SendType::ScheduledToUser
: Api::SendType::Scheduled, : Api::SendType::Scheduled),
SendMenu::Type::Disabled); SendMenu::Type::Disabled);
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
@ -429,7 +448,7 @@ void ScheduledWidget::sendingFilesConfirmed(
bool ctrlShiftEnter) { bool ctrlShiftEnter) {
Expects(list.filesToProcess.empty()); Expects(list.filesToProcess.empty());
if (showSendingFilesError(list)) { if (showSendingFilesError(list, way.sendImagesAsPhotos())) {
return; return;
} }
auto groups = DivideByGroups(std::move(list), way, false); auto groups = DivideByGroups(std::move(list), way, false);
@ -512,15 +531,19 @@ void ScheduledWidget::uploadFile(
bool ScheduledWidget::showSendingFilesError( bool ScheduledWidget::showSendingFilesError(
const Ui::PreparedList &list) const { const Ui::PreparedList &list) const {
return showSendingFilesError(list, std::nullopt);
}
bool ScheduledWidget::showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const {
const auto text = [&] { const auto text = [&] {
const auto error = Data::RestrictionError( using Error = Ui::PreparedList::Error;
_history->peer, const auto peer = _history->peer;
ChatRestriction::SendMedia); const auto error = Data::FileRestrictionError(peer, list, compress);
if (error) { if (error) {
return *error; return *error;
} } else switch (list.error) {
using Error = Ui::PreparedList::Error;
switch (list.error) {
case Error::None: return QString(); case Error::None: return QString();
case Error::EmptyFile: case Error::EmptyFile:
case Error::Directory: case Error::Directory:
@ -540,10 +563,7 @@ bool ScheduledWidget::showSendingFilesError(
return true; return true;
} }
Ui::ShowMultilineToast({ controller()->showToast({ text });
.parentOverride = Window::Show(controller()).toastParent(),
.text = { text },
});
return true; return true;
} }
@ -555,7 +575,21 @@ Api::SendAction ScheduledWidget::prepareSendAction(
} }
void ScheduledWidget::send() { void ScheduledWidget::send() {
if (_composeControls->getTextWithAppliedMarkdown().text.isEmpty()) { const auto textWithTags = _composeControls->getTextWithAppliedMarkdown();
if (textWithTags.text.isEmpty()) {
return;
}
const auto error = GetErrorTextForSending(
_history->peer,
{
.topicRootId = MsgId(),
.forward = nullptr,
.text = &textWithTags,
.ignoreSlowmodeCountdown = true,
});
if (!error.isEmpty()) {
controller()->showToast({ error });
return; return;
} }
const auto callback = [=](Api::SendOptions options) { send(options); }; const auto callback = [=](Api::SendOptions options) { send(options); };
@ -571,18 +605,6 @@ void ScheduledWidget::send(Api::SendOptions options) {
message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPageId = webPageId; message.webPageId = webPageId;
//const auto error = GetErrorTextForSending(
// _peer,
// _toForward,
// message.textWithTags);
//if (!error.isEmpty()) {
// Ui::ShowMultilineToast({
// .parentOverride = Window::Show(controller()).toastParent(),
// .text = { error },
// });
// return;
//}
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
_composeControls->clear(); _composeControls->clear();
@ -647,8 +669,9 @@ void ScheduledWidget::edit(
return; return;
} else if (!left.text.isEmpty()) { } else if (!left.text.isEmpty()) {
const auto remove = left.text.size(); const auto remove = left.text.size();
controller()->show(Ui::MakeInformBox( controller()->showToast({
tr::lng_edit_limit_reached(tr::now, lt_count, remove))); tr::lng_edit_limit_reached(tr::now, lt_count, remove)
});
return; return;
} }
@ -672,13 +695,13 @@ void ScheduledWidget::edit(
} }
if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) { if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} else if (error == u"MESSAGE_NOT_MODIFIED"_q) { } else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
_composeControls->cancelEditMessage(); _composeControls->cancelEditMessage();
} else if (error == u"MESSAGE_EMPTY"_q) { } else if (error == u"MESSAGE_EMPTY"_q) {
_composeControls->focus(); _composeControls->focus();
} else { } else {
controller()->show(Ui::MakeInformBox(tr::lng_edit_error())); controller()->showToast({ tr::lng_edit_error(tr::now) });
} }
update(); update();
return true; return true;
@ -712,9 +735,7 @@ bool ScheduledWidget::sendExistingDocument(
_history->peer, _history->peer,
ChatRestriction::SendStickers); ChatRestriction::SendStickers);
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} else if (ShowSendPremiumError(controller(), document)) { } else if (ShowSendPremiumError(controller(), document)) {
return false; return false;
@ -743,11 +764,9 @@ bool ScheduledWidget::sendExistingPhoto(
Api::SendOptions options) { Api::SendOptions options) {
const auto error = Data::RestrictionError( const auto error = Data::RestrictionError(
_history->peer, _history->peer,
ChatRestriction::SendMedia); ChatRestriction::SendPhotos);
if (error) { if (error) {
controller()->show( controller()->showToast({ *error });
Ui::MakeInformBox(*error),
Ui::LayerOption::KeepOther);
return false; return false;
} }
@ -765,7 +784,7 @@ void ScheduledWidget::sendInlineResult(
not_null<UserData*> bot) { not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(_history); const auto errorText = result->getErrorOnSend(_history);
if (!errorText.isEmpty()) { if (!errorText.isEmpty()) {
controller()->show(Ui::MakeInformBox(errorText)); controller()->showToast({ errorText });
return; return;
} }
const auto callback = [=](Api::SendOptions options) { const auto callback = [=](Api::SendOptions options) {

View file

@ -224,6 +224,9 @@ private:
std::optional<bool> overrideSendImagesAsPhotos, std::optional<bool> overrideSendImagesAsPhotos,
const QString &insertTextOnCancel = QString()); const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Ui::PreparedList &list) const; bool showSendingFilesError(const Ui::PreparedList &list) const;
bool showSendingFilesError(
const Ui::PreparedList &list,
std::optional<bool> compress) const;
void sendingFilesConfirmed( void sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,
Ui::SendFilesWay way, Ui::SendFilesWay way,

View file

@ -1033,8 +1033,8 @@ void TopBarWidget::updateControlsVisibility() {
const auto section = _activeChat.section; const auto section = _activeChat.section;
const auto historyMode = (section == Section::History); const auto historyMode = (section == Section::History);
const auto hasPollsMenu = (_activeChat.key.peer() const auto hasPollsMenu = (_activeChat.key.peer()
&& _activeChat.key.peer()->canSendPolls()) && Data::CanSend(_activeChat.key.peer(), ChatRestriction::SendPolls))
|| (topic && topic->canSendPolls()); || (topic && Data::CanSend(topic, ChatRestriction::SendPolls));
const auto hasTopicMenu = [&] { const auto hasTopicMenu = [&] {
if (!topic || section != Section::Replies) { if (!topic || section != Section::Replies) {
return false; return false;

View file

@ -145,7 +145,7 @@ void ShowChooseBox(
}; };
auto filter = [=](not_null<Data::Thread*> thread) -> bool { auto filter = [=](not_null<Data::Thread*> thread) -> bool {
const auto peer = thread->peer(); const auto peer = thread->peer();
if (!thread->canWrite()) { if (!Data::CanSend(thread, ChatRestriction::SendInline, false)) {
return false; return false;
} else if (const auto user = peer->asUser()) { } else if (const auto user = peer->asUser()) {
if (user->isBot()) { if (user->isBot()) {

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "history/history.h"
#include "history/history_item_reply_markup.h" #include "history/history_item_reply_markup.h"
#include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_layout_item.h"
#include "inline_bots/inline_bot_send_data.h" #include "inline_bots/inline_bot_send_data.h"
@ -367,7 +368,7 @@ bool Result::hasThumbDisplay() const {
}; };
void Result::addToHistory( void Result::addToHistory(
History *history, not_null<History*> history,
MessageFlags flags, MessageFlags flags,
MsgId msgId, MsgId msgId,
PeerId fromId, PeerId fromId,
@ -394,8 +395,13 @@ void Result::addToHistory(
std::move(markup)); std::move(markup));
} }
QString Result::getErrorOnSend(History *history) const { QString Result::getErrorOnSend(not_null<History*> history) const {
return sendData->getErrorOnSend(this, history); const auto specific = sendData->getErrorOnSend(this, history);
return !specific.isEmpty()
? specific
: Data::RestrictionError(
history->peer,
ChatRestriction::SendInline).value_or(QString());
} }
std::optional<Data::LocationPoint> Result::getLocationPoint() const { std::optional<Data::LocationPoint> Result::getLocationPoint() const {

View file

@ -63,7 +63,7 @@ public:
bool hasThumbDisplay() const; bool hasThumbDisplay() const;
void addToHistory( void addToHistory(
History *history, not_null<History*> history,
MessageFlags flags, MessageFlags flags,
MsgId msgId, MsgId msgId,
PeerId fromId, PeerId fromId,
@ -71,7 +71,7 @@ public:
UserId viaBotId, UserId viaBotId,
MsgId replyToId, MsgId replyToId,
const QString &postAuthor) const; const QString &postAuthor) const;
QString getErrorOnSend(History *history) const; QString getErrorOnSend(not_null<History*> history) const;
// interface for Layout:: usage // interface for Layout:: usage
std::optional<Data::LocationPoint> getLocationPoint() const; std::optional<Data::LocationPoint> getLocationPoint() const;

View file

@ -59,10 +59,8 @@ void SendDataCommon::addToHistory(
QString SendDataCommon::getErrorOnSend( QString SendDataCommon::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto error = Data::RestrictionError( const auto type = ChatRestriction::SendOther;
history->peer, return Data::RestrictionError(history->peer, type).value_or(QString());
ChatRestriction::SendMessages);
return error.value_or(QString());
} }
SendDataCommon::SentMessageFields SendText::getSentMessageFields() const { SendDataCommon::SentMessageFields SendText::getSentMessageFields() const {
@ -140,10 +138,8 @@ void SendPhoto::addToHistory(
QString SendPhoto::getErrorOnSend( QString SendPhoto::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto error = Data::RestrictionError( const auto type = ChatRestriction::SendPhotos;
history->peer, return Data::RestrictionError(history->peer, type).value_or(QString());
ChatRestriction::SendMedia);
return error.value_or(QString());
} }
void SendFile::addToHistory( void SendFile::addToHistory(
@ -173,24 +169,8 @@ void SendFile::addToHistory(
QString SendFile::getErrorOnSend( QString SendFile::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto errorMedia = Data::RestrictionError( const auto type = _document->requiredSendRight();
history->peer, return Data::RestrictionError(history->peer, type).value_or(QString());
ChatRestriction::SendMedia);
const auto errorStickers = Data::RestrictionError(
history->peer,
ChatRestriction::SendStickers);
const auto errorGifs = Data::RestrictionError(
history->peer,
ChatRestriction::SendGifs);
return errorMedia
? *errorMedia
: (errorStickers && (_document->sticker() != nullptr))
? *errorStickers
: (errorGifs
&& _document->isAnimation()
&& !_document->isVideoMessage())
? *errorGifs
: QString();
} }
void SendGame::addToHistory( void SendGame::addToHistory(
@ -219,10 +199,8 @@ void SendGame::addToHistory(
QString SendGame::getErrorOnSend( QString SendGame::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto error = Data::RestrictionError( const auto type = ChatRestriction::SendGames;
history->peer, return Data::RestrictionError(history->peer, type).value_or(QString());
ChatRestriction::SendGames);
return error.value_or(QString());
} }
SendDataCommon::SentMessageFields SendInvoice::getSentMessageFields() const { SendDataCommon::SentMessageFields SendInvoice::getSentMessageFields() const {

View file

@ -43,7 +43,7 @@ SendAsPeers::SendAsPeers(not_null<Session*> session)
bool SendAsPeers::shouldChoose(not_null<PeerData*> peer) { bool SendAsPeers::shouldChoose(not_null<PeerData*> peer) {
refresh(peer); refresh(peer);
return peer->canWrite(false) && (list(peer).size() > 1); return Data::CanSendAnything(peer, false) && (list(peer).size() > 1);
} }
void SendAsPeers::refresh(not_null<PeerData*> peer, bool force) { void SendAsPeers::refresh(not_null<PeerData*> peer, bool force) {

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -363,9 +364,13 @@ MainWidget::MainWidget(
const auto peer = key.peer(); const auto peer = key.peer();
const auto topic = key.topic(); const auto topic = key.topic();
auto canWrite = topic auto canWrite = topic
? Data::CanWriteValue(topic) ? Data::CanSendAnyOfValue(
topic,
Data::TabbedPanelSendRestrictions())
: peer : peer
? Data::CanWriteValue(peer) ? Data::CanSendAnyOfValue(
peer, Data::TabbedPanelSendRestrictions())
: rpl::single(false); : rpl::single(false);
return std::move( return std::move(
canWrite canWrite
@ -559,7 +564,7 @@ bool MainWidget::setForwardDraft(
.ignoreSlowmodeCountdown = true, .ignoreSlowmodeCountdown = true,
}); });
if (!error.isEmpty()) { if (!error.isEmpty()) {
Ui::show(Ui::MakeInformBox(error), Ui::LayerOption::KeepOther); _controller->show(Ui::MakeInformBox(error));
return false; return false;
} }
@ -575,7 +580,7 @@ bool MainWidget::shareUrl(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
const QString &url, const QString &url,
const QString &text) const { const QString &text) const {
if (!thread->canWrite()) { if (!Data::CanSendTexts(thread)) {
_controller->show(Ui::MakeInformBox(tr::lng_share_cant())); _controller->show(Ui::MakeInformBox(tr::lng_share_cant()));
return false; return false;
} }
@ -606,8 +611,8 @@ bool MainWidget::shareUrl(
bool MainWidget::inlineSwitchChosen( bool MainWidget::inlineSwitchChosen(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
const QString &botAndQuery) const { const QString &botAndQuery) const {
if (!thread->canWrite()) { if (!Data::CanSend(thread, ChatRestriction::SendInline)) {
Ui::show(Ui::MakeInformBox(tr::lng_inline_switch_cant())); _controller->show(Ui::MakeInformBox(tr::lng_inline_switch_cant()));
return false; return false;
} }
const auto textWithTags = TextWithTags{ const auto textWithTags = TextWithTags{
@ -637,13 +642,13 @@ bool MainWidget::inlineSwitchChosen(
bool MainWidget::sendPaths( bool MainWidget::sendPaths(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
const QStringList &paths) { const QStringList &paths) {
if (!thread->canWrite()) { if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant())); _controller->show(Ui::MakeInformBox(
tr::lng_forward_send_files_cant()));
return false; return false;
} else if (const auto error = Data::RestrictionError( } else if (const auto error = Data::AnyFileRestrictionError(
thread->peer(), thread->peer())) {
ChatRestriction::SendMedia)) { _controller->show(Ui::MakeInformBox(*error));
Ui::show(Ui::MakeInformBox(*error));
return false; return false;
} else { } else {
_controller->showThread( _controller->showThread(
@ -685,8 +690,13 @@ bool MainWidget::filesOrForwardDrop(
clearHider(_hider); clearHider(_hider);
} }
return false; return false;
} else if (!thread->canWrite()) { } else if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
Ui::show(Ui::MakeInformBox(tr::lng_forward_send_files_cant())); _controller->show(Ui::MakeInformBox(
tr::lng_forward_send_files_cant()));
return false;
} else if (const auto error = Data::AnyFileRestrictionError(
thread->peer())) {
_controller->show(Ui::MakeInformBox(*error));
return false; return false;
} else { } else {
_controller->showThread( _controller->showThread(
@ -2111,7 +2121,8 @@ void MainWidget::hideAll() {
void MainWidget::showAll() { void MainWidget::showAll() {
if (cPasswordRecovered()) { if (cPasswordRecovered()) {
cSetPasswordRecovered(false); cSetPasswordRecovered(false);
Ui::show(Ui::MakeInformBox(tr::lng_cloud_password_updated())); _controller->show(Ui::MakeInformBox(
tr::lng_cloud_password_updated()));
} }
if (isOneColumn()) { if (isOneColumn()) {
if (_sideShadow) { if (_sideShadow) {
@ -2720,7 +2731,7 @@ void MainWidget::activate() {
_controller, _controller,
paths[0].mid(interpret.size())); paths[0].mid(interpret.size()));
if (!error.isEmpty()) { if (!error.isEmpty()) {
Ui::show(Ui::MakeInformBox(error)); _controller->show(Ui::MakeInformBox(error));
} }
} else { } else {
const auto chosen = [=](not_null<Data::Thread*> thread) { const auto chosen = [=](not_null<Data::Thread*> thread) {

View file

@ -168,25 +168,22 @@ auto ActiveChat(not_null<Window::Controller*> controller) {
return Dialogs::Key(); return Dialogs::Key();
} }
bool CanWriteToActiveChat(not_null<Window::Controller*> controller) { bool CanSendToActiveChat(
not_null<Window::Controller*> controller,
ChatRestriction right) {
if (const auto topic = ActiveChat(controller).topic()) { if (const auto topic = ActiveChat(controller).topic()) {
return topic->canWrite(); return Data::CanSend(topic, right);
} else if (const auto history = ActiveChat(controller).history()) { } else if (const auto history = ActiveChat(controller).history()) {
return history->peer->canWrite(); return Data::CanSend(history->peer, right);
} }
return false; return false;
} }
std::optional<QString> RestrictionToSendStickers(not_null<PeerData*> peer) { std::optional<QString> RestrictionToSend(
return Data::RestrictionError( not_null<Window::Controller*> controller,
peer, ChatRestriction right) {
ChatRestriction::SendStickers);
}
std::optional<QString> RestrictionToSendStickers(
not_null<Window::Controller*> controller) {
if (const auto peer = ActiveChat(controller).peer()) { if (const auto peer = ActiveChat(controller).peer()) {
return RestrictionToSendStickers(peer); return Data::RestrictionError(peer, right);
} }
return std::nullopt; return std::nullopt;
} }
@ -364,10 +361,17 @@ void AppendEmojiPacks(
gesture.allowableMovement = 0; gesture.allowableMovement = 0;
[scrubber addGestureRecognizer:gesture]; [scrubber addGestureRecognizer:gesture];
if (const auto error = RestrictionToSendStickers(_controller)) { const auto kRight = ChatRestriction::SendStickers;
if (const auto error = RestrictionToSend(_controller, kRight)) {
_error = std::make_unique<PickerScrubberItem>( _error = std::make_unique<PickerScrubberItem>(
tr::lng_restricted_send_stickers_all(tr::now)); tr::lng_restricted_send_stickers_all(tr::now));
} }
} else {
const auto kRight = ChatRestriction::SendOther;
if (const auto error = RestrictionToSend(_controller, kRight)) {
_error = std::make_unique<PickerScrubberItem>(
tr::lng_restricted_send_message_all(tr::now));
}
} }
_lastPreviewedSticker = 0; _lastPreviewedSticker = 0;
@ -467,16 +471,19 @@ void AppendEmojiPacks(
- (void)scrubber:(NSScrubber*)scrubber - (void)scrubber:(NSScrubber*)scrubber
didSelectItemAtIndex:(NSInteger)index { didSelectItemAtIndex:(NSInteger)index {
if (!CanWriteToActiveChat(_controller) || _error) {
return;
}
scrubber.selectedIndex = -1; scrubber.selectedIndex = -1;
const auto sticker = _itemsDataSource->at(index, _type); const auto sticker = _itemsDataSource->at(index, _type);
const auto document = sticker.document; const auto document = sticker.document;
const auto emoji = sticker.emoji; const auto emoji = sticker.emoji;
const auto kRight = document
? ChatRestriction::SendStickers
: ChatRestriction::SendOther;
if (!CanSendToActiveChat(_controller, kRight) || _error) {
return;
}
auto callback = [=] { auto callback = [=] {
if (document) { if (document) {
if (const auto error = RestrictionToSendStickers(_controller)) { if (const auto error = RestrictionToSend(_controller, kRight)) {
_controller->show(Ui::MakeInformBox(*error)); _controller->show(Ui::MakeInformBox(*error));
return true; return true;
} else if (Window::ShowSendPremiumError(_controller->sessionController(), document)) { } else if (Window::ShowSendPremiumError(_controller->sessionController(), document)) {
@ -488,7 +495,10 @@ void AppendEmojiPacks(
document); document);
return true; return true;
} else if (emoji) { } else if (emoji) {
if (const auto inputField = qobject_cast<QTextEdit*>( if (const auto error = RestrictionToSend(_controller, kRight)) {
_controller->show(Ui::MakeInformBox(*error));
return true;
} else if (const auto inputField = qobject_cast<QTextEdit*>(
QApplication::focusWidget())) { QApplication::focusWidget())) {
Ui::InsertEmojiAtCursor(inputField->textCursor(), emoji); Ui::InsertEmojiAtCursor(inputField->textCursor(), emoji);
Core::App().settings().incrementRecentEmoji({ emoji }); Core::App().settings().incrementRecentEmoji({ emoji });
@ -562,9 +572,11 @@ void AppendEmojiPacks(
) | rpl::map([](Dialogs::Key k) { ) | rpl::map([](Dialogs::Key k) {
const auto topic = k.topic(); const auto topic = k.topic();
const auto peer = k.peer(); const auto peer = k.peer();
const auto right = ChatRestriction::SendStickers;
return peer return peer
&& !RestrictionToSendStickers(peer) && (topic
&& (topic ? topic->canWrite() : peer->canWrite()); ? Data::CanSend(topic, right)
: Data::CanSend(peer, right));
}) | rpl::distinct_until_changed( }) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool value) { ) | rpl::start_with_next([=](bool value) {
[self dismissPopover:nil]; [self dismissPopover:nil];

View file

@ -9,8 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" // ApiWrap::updateStickers() #include "apiwrap.h" // ApiWrap::updateStickers()
#include "core/application.h" #include "core/application.h"
#include "data/data_peer.h" // PeerData::canWrite() #include "data/data_chat_participant_status.h" // Data::CanSendAnyOf.
#include "data/data_forum_topic.h" // Data::ForumTopic::canWrite()
#include "data/data_session.h" #include "data/data_session.h"
#include "data/stickers/data_stickers.h" // Stickers::setsRef() #include "data/stickers/data_stickers.h" // Stickers::setsRef()
#include "main/main_domain.h" #include "main/main_domain.h"
@ -146,9 +145,11 @@ const auto kAudioItemIdentifier = @"touchbarAudio";
) | rpl::map([](Dialogs::Key k) { ) | rpl::map([](Dialogs::Key k) {
const auto topic = k.topic(); const auto topic = k.topic();
const auto peer = k.peer(); const auto peer = k.peer();
const auto rights = ChatRestriction::SendStickers
| ChatRestriction::SendOther;
return topic return topic
? topic->canWrite() ? Data::CanSendAnyOf(topic, rights)
: (peer && peer->canWrite()); : (peer && Data::CanSendAnyOf(peer, rights));
}) | rpl::distinct_until_changed() }) | rpl::distinct_until_changed()
) | rpl::start_with_next([=]( ) | rpl::start_with_next([=](
bool canApplyMarkdown, bool canApplyMarkdown,

View file

@ -35,6 +35,22 @@ bool PreparedFile::canBeInAlbumType(AlbumType album) const {
return CanBeInAlbumType(type, album); return CanBeInAlbumType(type, album);
} }
bool PreparedFile::isSticker() const {
Expects(information != nullptr);
return (type == PreparedFile::Type::Photo)
&& Core::IsMimeSticker(information->filemime);
}
bool PreparedFile::isGifv() const {
Expects(information != nullptr);
using Video = Ui::PreparedFileInformation::Video;
return (type == PreparedFile::Type::Video)
&& v::is<Video>(information->media)
&& v::get<Video>(information->media).isGifv;
}
AlbumType PreparedFile::albumType(bool sendImagesAsPhotos) const { AlbumType PreparedFile::albumType(bool sendImagesAsPhotos) const {
switch (type) { switch (type) {
case Type::Photo: case Type::Photo:
@ -207,13 +223,7 @@ bool PreparedList::canHaveEditorHintLabel() const {
} }
bool PreparedList::hasSticker() const { bool PreparedList::hasSticker() const {
for (const auto &file : files) { return ranges::any_of(files, &PreparedFile::isSticker);
if ((file.type == PreparedFile::Type::Photo)
&& Core::IsMimeSticker(file.information->filemime)) {
return true;
}
}
return false;
} }
int MaxAlbumItems() { int MaxAlbumItems() {

View file

@ -73,6 +73,8 @@ struct PreparedFile {
[[nodiscard]] bool canBeInAlbumType(AlbumType album) const; [[nodiscard]] bool canBeInAlbumType(AlbumType album) const;
[[nodiscard]] AlbumType albumType(bool sendImagesAsPhotos) const; [[nodiscard]] AlbumType albumType(bool sendImagesAsPhotos) const;
[[nodiscard]] bool isSticker() const;
[[nodiscard]] bool isGifv() const;
QString path; QString path;
QByteArray content; QByteArray content;

View file

@ -836,7 +836,8 @@ Manager::DisplayOptions Manager::getNotificationOptions(
|| !item || !item
|| ((item->out() || peer->isSelf()) && item->isFromScheduled()); || ((item->out() || peer->isSelf()) && item->isFromScheduled());
result.hideReplyButton = result.hideMarkAsRead result.hideReplyButton = result.hideMarkAsRead
|| (!peer->canWrite() && (!topic || !topic->canWrite())) || (!Data::CanSendTexts(peer)
&& (!topic || !Data::CanSendTexts(topic)))
|| peer->isBroadcast() || peer->isBroadcast()
|| (peer->slowmodeSecondsLeft() > 0); || (peer->slowmodeSecondsLeft() > 0);
return result; return result;

View file

@ -951,7 +951,11 @@ void Filler::addManageChat() {
} }
void Filler::addCreatePoll() { void Filler::addCreatePoll() {
if (!(_topic ? _topic->canSendPolls() : _peer->canSendPolls())) { constexpr auto kRight = ChatRestriction::SendPolls;
const auto can = _topic
? Data::CanSend(_topic, kRight)
: Data::CanSend(_peer, kRight);
if (!can) {
return; return;
} }
const auto peer = _peer; const auto peer = _peer;
@ -1329,7 +1333,7 @@ void PeerMenuShareContactBox(
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>(); const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto callback = [=](not_null<Data::Thread*> thread) { auto callback = [=](not_null<Data::Thread*> thread) {
const auto peer = thread->peer(); const auto peer = thread->peer();
if (!thread->canWrite()) { if (!Data::CanSend(thread, ChatRestriction::SendOther)) {
navigation->parentController()->show( navigation->parentController()->show(
Ui::MakeInformBox(tr::lng_forward_share_cant()), Ui::MakeInformBox(tr::lng_forward_share_cant()),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
@ -1946,10 +1950,9 @@ QPointer<Ui::BoxContent> ShowShareGameBox(
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
}; };
auto filter = [](not_null<Data::Thread*> thread) { auto filter = [](not_null<Data::Thread*> thread) {
const auto peer = thread->peer(); return !thread->peer()->isSelf()
return (thread->canWrite() || thread->asForum()) && (Data::CanSend(thread, ChatRestriction::SendGames)
&& !peer->amRestricted(ChatRestriction::SendGames) || thread->asForum());
&& !peer->isSelf();
}; };
auto initBox = [](not_null<PeerListBox*> box) { auto initBox = [](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] { box->addButton(tr::lng_cancel(), [box] {

View file

@ -1871,6 +1871,13 @@ void SessionController::hideLayer(anim::type animated) {
_window->hideLayer(animated); _window->hideLayer(animated);
} }
void SessionController::showToast(TextWithEntities &&text) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(this).toastParent(),
.text = std::move(text),
});
}
void SessionController::openPhoto( void SessionController::openPhoto(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
FullMsgId contextId, FullMsgId contextId,

View file

@ -337,9 +337,10 @@ public:
object_ptr<Ui::BoxContent> content, object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther, Ui::LayerOptions options = Ui::LayerOption::KeepOther,
anim::type animated = anim::type::normal); anim::type animated = anim::type::normal);
void hideLayer(anim::type animated = anim::type::normal); void hideLayer(anim::type animated = anim::type::normal);
void showToast(TextWithEntities &&text);
[[nodiscard]] auto sendingAnimation() const [[nodiscard]] auto sendingAnimation() const
-> Ui::MessageSendingAnimationController &; -> Ui::MessageSendingAnimationController &;
[[nodiscard]] auto tabbedSelector() const [[nodiscard]] auto tabbedSelector() const