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_polls" = "Send polls";
"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_forever" = "Forever";
"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_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_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_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_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_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_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_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_gifs_all" = "Posting GIFs 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_banned_view_messages" = "Read 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_embed_links" = "Embed links";
"lng_admin_log_banned_send_polls" = "Send polls";

View file

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

View file

@ -534,9 +534,8 @@ auto ChooseRecipientBoxController::createRow(
const auto peer = history->peer;
const auto skip = _filter
? !_filter(history)
: ((peer->isBroadcast() && !peer->canWrite())
|| (peer->isUser() && !peer->canWrite())
|| peer->isRepliesChat());
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| (peer->isUser() && !Data::CanSendAnything(peer)));
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)
-> 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);
};

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <rpl/variable.h>
#include "base/flags.h"
#include "boxes/abstract_box.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h"
@ -48,6 +48,30 @@ namespace SendMenu {
enum class Type;
} // 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 {
public:
enum class SendLimit {
@ -59,7 +83,8 @@ public:
not_null<Window::SessionController*> controller,
Ui::PreparedList &&list,
const TextWithTags &caption,
not_null<PeerData*> peer,
SendFilesLimits limits,
SendFilesCheck check,
Api::SendType sendType,
SendMenu::Type sendMenuType);
@ -126,6 +151,13 @@ private:
[[nodiscard]] bool hasSendMenu() const;
[[nodiscard]] bool hasSpoilerMenu() const;
[[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 applyBlockChanges();
void toggleSpoilers(bool enabled);
@ -177,10 +209,10 @@ private:
Ui::PreparedList _list;
std::optional<int> _removingIndex;
SendLimit _sendLimit = SendLimit::Many;
SendFilesLimits _limits = {};
SendMenu::Type _sendMenuType = SendMenu::Type();
bool _allowEmojiWithoutPremium = false;
SendFilesCheck _check;
Fn<void(
Ui::PreparedList &&list,
Ui::SendFilesWay way,

View file

@ -1120,10 +1120,14 @@ void ShareBox::Inner::chooseForumTopic(not_null<Data::Forum*> forum) {
box->closeBox();
}, box->lifetime());
};
auto filter = [=](not_null<Data::ForumTopic*> topic) {
return guard && _descriptor.filterCallback(topic);
};
auto box = Box<PeerListBox>(
std::make_unique<ChooseTopicBoxController>(
forum,
std::move(chosen)),
std::move(chosen),
std::move(filter)),
std::move(initBox));
*weak = box.data();
_show->showBox(std::move(box));
@ -1507,8 +1511,12 @@ void FastShareMessage(
}
};
auto filterCallback = [isGame](not_null<Data::Thread*> thread) {
return thread->canWrite()
const auto requiredRight = item->requiredSendRight();
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());
};
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::combine(
Data::CanWriteValue(peer, false),
Data::CanSendValue(peer, ChatRestriction::SendOther, false),
_call->joinAsValue()
) | rpl::map([=](bool can, not_null<PeerData*> joinAs) {
return can && joinAs->isSelf();

View file

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

View file

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

View file

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

View file

@ -321,13 +321,18 @@ ChatRestrictionsInfo ChannelData::KickedRestrictedRights(
not_null<PeerData*> participant) {
using Flag = ChatRestriction;
const auto flags = Flag::ViewMessages
| Flag::SendMessages
| Flag::SendMedia
| Flag::EmbedLinks
| Flag::SendStickers
| Flag::SendGifs
| Flag::SendGames
| Flag::SendInline;
| Flag::SendInline
| Flag::SendPhotos
| Flag::SendVideos
| Flag::SendVideoMessages
| Flag::SendMusic
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther
| Flag::EmbedLinks;
return ChatRestrictionsInfo(
(participant->isUser() ? flags : Flag::ViewMessages),
std::numeric_limits<int32>::max());
@ -549,10 +554,6 @@ bool ChannelData::canAddMembers() const {
: ((adminRights() & AdminRight::InviteByLinkOrAdd) || amCreator());
}
bool ChannelData::canSendPolls() const {
return canWrite() && !amRestricted(ChatRestriction::SendPolls);
}
bool ChannelData::canAddAdmins() const {
return amCreator()
|| (adminRights() & AdminRight::AddAdmins);
@ -563,18 +564,6 @@ bool ChannelData::canPublish() const {
|| (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 {
return !(flags() & Flag::NoForwards);
}

View file

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

View file

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

View file

@ -100,7 +100,6 @@ public:
not_null<UserData*> user);
// Like in ChannelData.
[[nodiscard]] bool canWrite() const;
[[nodiscard]] bool allowsForwarding() const;
[[nodiscard]] bool canEditInformation() const;
[[nodiscard]] bool canEditPermissions() const;
@ -110,7 +109,6 @@ public:
[[nodiscard]] bool canAddMembers() const;
[[nodiscard]] bool canAddAdmins() const;
[[nodiscard]] bool canBanMembers() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] bool anyoneCanAddMembers() const;
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 "base/unixtime.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 {
@ -53,4 +61,313 @@ std::vector<ChatRestrictions> ListOfRestrictions(
| 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

View file

@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
enum class UserRestriction {
SendVoiceMessages,
SendVideoMessages,
};
namespace Ui {
struct PreparedList;
struct PreparedFile;
} // namespace Ui
enum class ChatAdminRight {
ChangeInfo = (1 << 0),
@ -31,14 +31,22 @@ using ChatAdminRights = base::flags<ChatAdminRight>;
enum class ChatRestriction {
ViewMessages = (1 << 0),
SendMessages = (1 << 1),
SendMedia = (1 << 2),
SendStickers = (1 << 3),
SendGifs = (1 << 4),
SendGames = (1 << 5),
SendInline = (1 << 6),
EmbedLinks = (1 << 7),
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),
AddParticipants = (1 << 15),
PinMessages = (1 << 17),
@ -70,6 +78,8 @@ struct ChatRestrictionsInfo {
namespace Data {
class Thread;
struct AdminRightsSetOptions {
bool isGroup : 1 = false;
bool isForum : 1 = false;
@ -83,4 +93,97 @@ struct RestrictionsSetOptions {
[[nodiscard]] std::vector<ChatRestrictions> ListOfRestrictions(
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

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) {
_filename = remoteFileName;

View file

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

View file

@ -249,18 +249,6 @@ bool ForumTopic::my() const {
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 {
return my() || channel()->canManageTopics();
}

View file

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

View file

@ -460,10 +460,6 @@ bool Media::forceForwardedInfo() const {
return false;
}
QString Media::errorTextForForward(not_null<PeerData*> peer) const {
return QString();
}
bool Media::hasSpoiler() const {
return false;
}
@ -686,13 +682,6 @@ bool MediaPhoto::allowsEditMedia() const {
return true;
}
QString MediaPhoto::errorTextForForward(not_null<PeerData*> peer) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendMedia
).value_or(QString());
}
bool MediaPhoto::hasSpoiler() const {
return _spoiler;
}
@ -1040,46 +1029,6 @@ bool MediaFile::dropForwardedInfo() const {
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 {
return _spoiler;
}
@ -1589,13 +1538,6 @@ TextForMimeData MediaGame::clipboardText() const {
return TextForMimeData();
}
QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendGames
).value_or(QString());
}
bool MediaGame::dropForwardedInfo() const {
return true;
}
@ -1773,16 +1715,6 @@ TextForMimeData MediaPoll::clipboardText() const {
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) {
return false;
}
@ -1888,7 +1820,7 @@ ClickHandlerPtr MediaDice::MakeHandler(
.durationMs = Ui::Toast::kDefaultDuration * 2,
.multiline = true,
};
if (history->peer->canWrite()) {
if (Data::CanSend(history->peer, ChatRestriction::SendOther)) {
auto link = Ui::Text::Link(
tr::lng_about_random_send(tr::now).toUpper());
link.entities.push_back(

View file

@ -128,7 +128,6 @@ public:
virtual bool forwardedBecomesUnread() const;
virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const;
virtual QString errorTextForForward(not_null<PeerData*> peer) const;
[[nodiscard]] virtual bool hasSpoiler() const;
[[nodiscard]] virtual bool consumeMessageText(
@ -190,7 +189,6 @@ public:
TextForMimeData clipboardText() const override;
bool allowsEditCaption() const override;
bool allowsEditMedia() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool hasSpoiler() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -234,7 +232,6 @@ public:
bool allowsEditMedia() const override;
bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool hasSpoiler() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -395,7 +392,6 @@ public:
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool dropForwardedInfo() const override;
bool consumeMessageText(const TextWithEntities &text) override;
@ -460,7 +456,6 @@ public:
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool updateInlineResultMedia(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().
bool PeerData::canPinMessages() const {
if (const auto user = asUser()) {
return user->flags() & UserDataFlag::CanPinMessages;
return !user->amRestricted(ChatRestriction::PinMessages);
} else if (const auto chat = asChat()) {
return chat->amIn()
&& !chat->amRestricted(ChatRestriction::PinMessages);
@ -881,18 +881,6 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const {
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 {
if (const auto user = asUser()) {
return true;
@ -920,10 +908,26 @@ Data::RestrictionCheckResult PeerData::amRestricted(
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()
| (channel->isPublic()
? (ChatRestriction::PinMessages | ChatRestriction::ChangeInfo)
? (ChatRestriction::PinMessages
| ChatRestriction::ChangeInfo)
: ChatRestrictions(0));
return (channel->amCreator() || allowByAdminRights(right, channel))
? Result::Allowed()
@ -1010,19 +1014,6 @@ int PeerData::slowmodeSecondsLeft() const {
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 {
if (const auto chat = asChat()) {
return chat->amCreator()
@ -1109,95 +1100,6 @@ void PeerData::setMessagesTTL(TimeId period) {
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(
not_null<PeerData*> peer,
MsgId messageId) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -255,8 +255,17 @@ QString GeneratePermissionsChangeText(
static auto phraseMap = std::map<Flags, tr::phrase<>>{
{ Flag::ViewMessages, tr::lng_admin_log_banned_view_messages },
{ Flag::SendMessages, tr::lng_admin_log_banned_send_messages },
{ Flag::SendMedia, tr::lng_admin_log_banned_send_media },
{ Flag::SendOther, tr::lng_admin_log_banned_send_messages },
{ 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::SendGifs
| Flag::SendInline

View file

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

View file

@ -2070,7 +2070,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto canReply = [&] {
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic ? topic->canWrite() : peer->canWrite();
return topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer);
}();
if (canReply) {
_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_user.h"
#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_web_page.h"
#include "chat_helpers/stickers_gift_box_pack.h"
@ -1836,9 +1837,9 @@ bool HistoryItem::canBeEdited() const {
if (isPost()) {
return channel->canPublish();
} else if (const auto topic = this->topic()) {
return topic->canWrite();
return Data::CanSendAnything(topic);
} else {
return channel->canWrite();
return Data::CanSendAnything(channel);
}
} else {
return false;
@ -1958,6 +1959,53 @@ bool HistoryItem::suggestDeleteAllReport() const {
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 {
if (!isRegular() || isService()) {
return false;

View file

@ -393,6 +393,10 @@ public:
[[nodiscard]] bool suggestReport() const;
[[nodiscard]] bool suggestBanReport() 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;
enum class ReactionSource {

View file

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

View file

@ -430,6 +430,12 @@ private:
Ui::PreparedList &&list,
const QString &insertTextOnCancel = QString());
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(
Ui::PreparedList &&list,

View file

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

View file

@ -150,6 +150,7 @@ public:
[[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] bool readyToForward() const;
[[nodiscard]] const HistoryItemsList &forwardItems() const;
[[nodiscard]] FullMsgId replyingToMessage() 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;
if (!item
|| !item->isRegular()
|| (topic ? !topic->canWrite() : !peer->canWrite())
|| !(topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer))
|| (context != Context::History && context != Context::Replies)) {
return false;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -145,7 +145,7 @@ void ShowChooseBox(
};
auto filter = [=](not_null<Data::Thread*> thread) -> bool {
const auto peer = thread->peer();
if (!thread->canWrite()) {
if (!Data::CanSend(thread, ChatRestriction::SendInline, false)) {
return false;
} else if (const auto user = peer->asUser()) {
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_photo_media.h"
#include "data/data_document_media.h"
#include "history/history.h"
#include "history/history_item_reply_markup.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "inline_bots/inline_bot_send_data.h"
@ -367,7 +368,7 @@ bool Result::hasThumbDisplay() const {
};
void Result::addToHistory(
History *history,
not_null<History*> history,
MessageFlags flags,
MsgId msgId,
PeerId fromId,
@ -394,8 +395,13 @@ void Result::addToHistory(
std::move(markup));
}
QString Result::getErrorOnSend(History *history) const {
return sendData->getErrorOnSend(this, history);
QString Result::getErrorOnSend(not_null<History*> history) const {
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 {

View file

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

View file

@ -59,10 +59,8 @@ void SendDataCommon::addToHistory(
QString SendDataCommon::getErrorOnSend(
const Result *owner,
not_null<History*> history) const {
const auto error = Data::RestrictionError(
history->peer,
ChatRestriction::SendMessages);
return error.value_or(QString());
const auto type = ChatRestriction::SendOther;
return Data::RestrictionError(history->peer, type).value_or(QString());
}
SendDataCommon::SentMessageFields SendText::getSentMessageFields() const {
@ -140,10 +138,8 @@ void SendPhoto::addToHistory(
QString SendPhoto::getErrorOnSend(
const Result *owner,
not_null<History*> history) const {
const auto error = Data::RestrictionError(
history->peer,
ChatRestriction::SendMedia);
return error.value_or(QString());
const auto type = ChatRestriction::SendPhotos;
return Data::RestrictionError(history->peer, type).value_or(QString());
}
void SendFile::addToHistory(
@ -173,24 +169,8 @@ void SendFile::addToHistory(
QString SendFile::getErrorOnSend(
const Result *owner,
not_null<History*> history) const {
const auto errorMedia = Data::RestrictionError(
history->peer,
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();
const auto type = _document->requiredSendRight();
return Data::RestrictionError(history->peer, type).value_or(QString());
}
void SendGame::addToHistory(
@ -219,10 +199,8 @@ void SendGame::addToHistory(
QString SendGame::getErrorOnSend(
const Result *owner,
not_null<History*> history) const {
const auto error = Data::RestrictionError(
history->peer,
ChatRestriction::SendGames);
return error.value_or(QString());
const auto type = ChatRestriction::SendGames;
return Data::RestrictionError(history->peer, type).value_or(QString());
}
SendDataCommon::SentMessageFields SendInvoice::getSentMessageFields() const {

View file

@ -43,7 +43,7 @@ SendAsPeers::SendAsPeers(not_null<Session*> session)
bool SendAsPeers::shouldChoose(not_null<PeerData*> 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) {

View file

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

View file

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

View file

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

View file

@ -35,6 +35,22 @@ bool PreparedFile::canBeInAlbumType(AlbumType album) const {
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 {
switch (type) {
case Type::Photo:
@ -207,13 +223,7 @@ bool PreparedList::canHaveEditorHintLabel() const {
}
bool PreparedList::hasSticker() const {
for (const auto &file : files) {
if ((file.type == PreparedFile::Type::Photo)
&& Core::IsMimeSticker(file.information->filemime)) {
return true;
}
}
return false;
return ranges::any_of(files, &PreparedFile::isSticker);
}
int MaxAlbumItems() {

View file

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

View file

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

View file

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

View file

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

View file

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