Edit paid messages exceptions.

This commit is contained in:
John Preston 2025-02-26 17:24:07 +04:00
parent 1684465e04
commit c6fd8bcb99
11 changed files with 184 additions and 48 deletions

View file

@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
}
Unexpected("Key in Api::UserPrivacy::KetToTL.");
}
@ -241,6 +242,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
case mtpc_privacyKeyNoPaidMessages:
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
}
return std::nullopt;
}

View file

@ -32,6 +32,7 @@ public:
About,
Birthday,
GiftsAutoSave,
NoPaidMessages,
};
enum class Option {
Everyone,

View file

@ -523,6 +523,7 @@ void ApiWrap::sendMessageFail(
uint64 randomId,
FullMsgId itemId) {
const auto show = ShowForPeer(peer);
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
if (show && error == u"PEER_FLOOD"_q) {
show->showBox(
Ui::MakeInformBox(
@ -577,11 +578,19 @@ void ApiWrap::sendMessageFail(
if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now));
}
} else if (error.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
} else if (error.startsWith(paidStarsPrefix)) {
if (show) {
show->showToast(
u"Payment requirements changed. Please, try again."_q);
}
if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) {
if (const auto user = peer->asUser()) {
user->setStarsPerMessage(stars);
} else if (const auto channel = peer->asChannel()) {
channel->setStarsPerMessage(stars);
}
}
peer->updateFull();
}
if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0);

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "boxes/peer_list_controllers.h"
#include "settings/settings_premium.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h"
#include "calls/calls_instance.h"
#include "lang/lang_keys.h"
@ -561,6 +562,39 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
return result;
}
void EditNoPaidMessagesExceptions(
not_null<Window::SessionController*> window,
const Api::UserPrivacy::Rule &value) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
&window->session(),
tr::lng_messages_privacy_remove_fee(),
value.always,
std::optional<SpecialRowType>());
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), [=] {
auto copy = value;
auto &setTo = copy.always;
setTo.peers = box->collectSelectedRows();
setTo.premiums = false;
setTo.miniapps = false;
auto &removeFrom = copy.never;
for (const auto peer : setTo.peers) {
removeFrom.peers.erase(
ranges::remove(removeFrom.peers, peer),
end(removeFrom.peers));
}
window->session().api().userPrivacy().save(
Api::UserPrivacy::Key::NoPaidMessages,
copy);
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
window->show(
Box<PeerListBox>(std::move(controller), std::move(initBox)));
}
} // namespace
bool EditPrivacyController::hasOption(Option option) const {
@ -923,11 +957,12 @@ void EditMessagesPrivacyBox(
constexpr auto kOptionPremium = 1;
constexpr auto kOptionCharge = 2;
const auto session = &controller->session();
const auto allowed = [=] {
return controller->session().premium()
|| controller->session().appConfig().newRequirePremiumFree();
return session->premium()
|| session->appConfig().newRequirePremiumFree();
};
const auto privacy = &controller->session().api().globalPrivacy();
const auto privacy = &session->api().globalPrivacy();
const auto inner = box->verticalLayout();
inner->add(object_ptr<Ui::PlainShadow>(box));
@ -995,18 +1030,45 @@ void EditMessagesPrivacyBox(
state->stars = SetupChargeSlider(
chargeInner,
controller->session().user(),
session->user(),
savedValue);
Ui::AddSkip(chargeInner);
Ui::AddSubsectionTitle(
chargeInner,
tr::lng_messages_privacy_exceptions());
const auto key = Api::UserPrivacy::Key::NoPaidMessages;
session->api().userPrivacy().reload(key);
auto label = session->api().userPrivacy().value(
key
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
using namespace Settings;
const auto always = ExceptionUsersCount(value.always.peers);
return always
? tr::lng_edit_privacy_exceptions_count(
tr::now,
lt_count,
always)
: QString();
});
const auto exceptions = Settings::AddButtonWithLabel(
chargeInner,
tr::lng_messages_privacy_remove_fee(),
rpl::single(u""_q),
std::move(label),
st::settingsButtonNoIcon);
const auto shower = exceptions->lifetime().make_state<rpl::lifetime>();
exceptions->setClickedCallback([=] {
*shower = session->api().userPrivacy().value(
key
) | rpl::take(
1
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
EditNoPaidMessagesExceptions(controller, value);
});
});
Ui::AddSkip(chargeInner);
Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about());

View file

@ -869,15 +869,11 @@ int ChannelData::starsPerMessage() const {
}
void ChannelData::setStarsPerMessage(int stars) {
if (!mgInfo || starsPerMessage() == stars) {
return;
}
const auto removed = mgInfo->_starsPerMessage && !stars;
mgInfo->_starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
if (removed) {
session().local().clearPeerTrusted(id);
if (mgInfo && starsPerMessage() != stars) {
mgInfo->_starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
checkTrustedPayForMessage();
}
int ChannelData::peerGiftsCount() const {

View file

@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/history_item.h"
#include "storage/file_download.h"
#include "storage/storage_account.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
@ -341,6 +342,17 @@ void PeerData::invalidateEmptyUserpic() {
_userpicEmpty = nullptr;
}
void PeerData::checkTrustedPayForMessage() {
if (!_checkedTrustedPayForMessage
&& !starsPerMessage()
&& session().local().peerTrustedPayForMessageRead()) {
_checkedTrustedPayForMessage = 1;
if (session().local().hasPeerTrustedPayForMessageEntry(id)) {
session().local().clearPeerTrustedPayForMessage(id);
}
}
}
ClickHandlerPtr PeerData::createOpenLink() {
return std::make_shared<PeerClickHandler>(this);
}

View file

@ -504,6 +504,7 @@ protected:
void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo);
void clearUserpic();
void invalidateEmptyUserpic();
void checkTrustedPayForMessage();
private:
void fillNames();
@ -538,9 +539,10 @@ private:
crl::time _lastFullUpdate = 0;
QString _name;
uint32 _nameVersion : 30 = 1;
uint32 _nameVersion : 29 = 1;
uint32 _sensitiveContent : 1 = 0;
uint32 _wallPaperOverriden : 1 = 0;
uint32 _checkedTrustedPayForMessage : 1 = 0;
TimeId _ttlPeriod = 0;

View file

@ -543,13 +543,10 @@ int UserData::starsPerMessage() const {
void UserData::setStarsPerMessage(int stars) {
if (_starsPerMessage != stars) {
const auto removed = _starsPerMessage && !stars;
_starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
if (removed) {
session().local().clearPeerTrusted(id);
}
}
checkTrustedPayForMessage();
}
bool UserData::canAddContact() const {

View file

@ -284,7 +284,7 @@ void ShowSendPaidConfirm(
const auto singlePeer = (peers.size() > 1)
? (PeerData*)nullptr
: peers.front().get();
const auto recipientId = singlePeer ? singlePeer->id : PeerId();
const auto singlePeerId = singlePeer ? singlePeer->id : PeerId();
const auto check = [=] {
const auto required = details.stars;
if (!required) {
@ -299,7 +299,7 @@ void ShowSendPaidConfirm(
Settings::MaybeRequestBalanceIncrease(
show,
required,
Settings::SmallBalanceForMessage{ .recipientId = recipientId },
Settings::SmallBalanceForMessage{ .recipientId = singlePeerId },
done);
};
auto usersOnly = true;
@ -309,9 +309,15 @@ void ShowSendPaidConfirm(
break;
}
}
const auto singlePeerStars = singlePeer
? singlePeer->starsPerMessageChecked()
: 0;
if (singlePeer) {
const auto session = &singlePeer->session();
if (session->local().isPeerTrustedPayForMessage(recipientId)) {
const auto trusted = session->local().isPeerTrustedPayForMessage(
singlePeerId,
singlePeerStars);
if (trusted) {
check();
return;
}
@ -323,7 +329,9 @@ void ShowSendPaidConfirm(
const auto proceed = [=](Fn<void()> close) {
if (singlePeer && (*trust)->checked()) {
const auto session = &singlePeer->session();
session->local().markPeerTrustedPayForMessage(recipientId);
session->local().markPeerTrustedPayForMessage(
singlePeerId,
singlePeerStars);
}
check();
close();

View file

@ -3148,7 +3148,7 @@ void Account::readSelf(
}
void Account::writeTrustedPeers() {
if (_trustedPeers.empty()) {
if (_trustedPeers.empty() && _trustedPayPerMessage.empty()) {
if (_trustedPeersKey) {
ClearKey(_trustedPeersKey, _basePath);
_trustedPeersKey = 0;
@ -3160,7 +3160,10 @@ void Account::writeTrustedPeers() {
_trustedPeersKey = GenerateKey(_basePath);
writeMapQueued();
}
quint32 size = sizeof(qint32) + _trustedPeers.size() * sizeof(quint64);
quint32 size = sizeof(qint32)
+ _trustedPeers.size() * sizeof(quint64)
+ sizeof(qint32)
+ _trustedPayPerMessage.size() * (sizeof(quint64) + sizeof(qint32));
EncryptedDescriptor data(size);
data.stream << qint32(_trustedPeers.size());
for (const auto &[peerId, mask] : _trustedPeers) {
@ -3170,6 +3173,10 @@ void Account::writeTrustedPeers() {
value |= (quint64(mask) << 56);
data.stream << value;
}
data.stream << qint32(_trustedPayPerMessage.size());
for (const auto &[peerId, stars] : _trustedPayPerMessage) {
data.stream << SerializePeerId(peerId) << qint32(stars);
}
FileWriteDescriptor file(_trustedPeersKey, _basePath);
file.writeEncrypted(data, _localKey);
@ -3192,9 +3199,9 @@ void Account::readTrustedPeers() {
return;
}
qint32 size = 0;
trusted.stream >> size;
for (int i = 0; i < size; ++i) {
qint32 trustedCount = 0;
trusted.stream >> trustedCount;
for (int i = 0; i < trustedCount; ++i) {
auto value = quint64();
trusted.stream >> value;
const auto mask = base::flags<PeerTrustFlag>::from_raw(
@ -3203,6 +3210,28 @@ void Account::readTrustedPeers() {
const auto peerId = DeserializePeerId(peerIdSerialized);
_trustedPeers.emplace(peerId, mask);
}
if (trusted.stream.atEnd()) {
return;
}
qint32 payPerMessageCount = 0;
trusted.stream >> payPerMessageCount;
const auto owner = _owner->sessionExists()
? &_owner->session().data()
: nullptr;
for (int i = 0; i < payPerMessageCount; ++i) {
auto value = quint64();
auto stars = qint32();
trusted.stream >> value >> stars;
const auto peerId = DeserializePeerId(value);
const auto peer = owner ? owner->peerLoaded(peerId) : nullptr;
const auto now = peer ? peer->starsPerMessage() : stars;
if (now > 0 && now <= stars) {
_trustedPayPerMessage.emplace(peerId, stars);
}
}
if (_trustedPayPerMessage.size() != payPerMessageCount) {
writeTrustedPeers();
}
}
void Account::markPeerTrustedOpenGame(PeerId peerId) {
@ -3269,32 +3298,45 @@ bool Account::isPeerTrustedOpenWebView(PeerId peerId) {
&& ((i->second & PeerTrustFlag::OpenWebView) != 0);
}
void Account::markPeerTrustedPayForMessage(PeerId peerId) {
if (isPeerTrustedPayForMessage(peerId)) {
void Account::markPeerTrustedPayForMessage(
PeerId peerId,
int starsPerMessage) {
if (isPeerTrustedPayForMessage(peerId, starsPerMessage)) {
return;
}
const auto i = _trustedPeers.find(peerId);
if (i == end(_trustedPeers)) {
_trustedPeers.emplace(
peerId,
PeerTrustFlag::NoOpenGame | PeerTrustFlag::PayForMessage);
const auto i = _trustedPayPerMessage.find(peerId);
if (i == end(_trustedPayPerMessage)) {
_trustedPayPerMessage.emplace(peerId, starsPerMessage);
} else {
i->second |= PeerTrustFlag::PayForMessage;
i->second = starsPerMessage;
}
writeTrustedPeers();
}
bool Account::isPeerTrustedPayForMessage(PeerId peerId) {
bool Account::isPeerTrustedPayForMessage(
PeerId peerId,
int starsPerMessage) {
if (starsPerMessage <= 0) {
return true;
}
readTrustedPeers();
const auto i = _trustedPeers.find(peerId);
return (i != end(_trustedPeers))
&& ((i->second & PeerTrustFlag::PayForMessage) != 0);
const auto i = _trustedPayPerMessage.find(peerId);
return (i != end(_trustedPayPerMessage))
&& (i->second >= starsPerMessage);
}
void Account::clearPeerTrusted(PeerId peerId) {
const auto i = _trustedPeers.find(peerId);
if (i != end(_trustedPeers)) {
_trustedPeers.erase(i);
bool Account::peerTrustedPayForMessageRead() const {
return _trustedPeersRead;
}
bool Account::hasPeerTrustedPayForMessageEntry(PeerId peerId) const {
return _trustedPayPerMessage.contains(peerId);
}
void Account::clearPeerTrustedPayForMessage(PeerId peerId) {
const auto i = _trustedPayPerMessage.find(peerId);
if (i != end(_trustedPayPerMessage)) {
_trustedPayPerMessage.erase(i);
writeTrustedPeers();
}
}

View file

@ -173,9 +173,13 @@ public:
[[nodiscard]] bool isPeerTrustedPayment(PeerId peerId);
void markPeerTrustedOpenWebView(PeerId peerId);
[[nodiscard]] bool isPeerTrustedOpenWebView(PeerId peerId);
void markPeerTrustedPayForMessage(PeerId peerId);
[[nodiscard]] bool isPeerTrustedPayForMessage(PeerId peerId);
void clearPeerTrusted(PeerId peerId);
void markPeerTrustedPayForMessage(PeerId peerId, int starsPerMessage);
[[nodiscard]] bool isPeerTrustedPayForMessage(
PeerId peerId,
int starsPerMessage);
[[nodiscard]] bool peerTrustedPayForMessageRead() const;
[[nodiscard]] bool hasPeerTrustedPayForMessageEntry(PeerId peerId) const;
void clearPeerTrustedPayForMessage(PeerId peerId);
void enforceModernStorageIdBots();
[[nodiscard]] Webview::StorageId resolveStorageIdBots();
@ -210,7 +214,6 @@ private:
NoOpenGame = (1 << 0),
Payment = (1 << 1),
OpenWebView = (1 << 2),
PayForMessage = (1 << 3),
};
friend inline constexpr bool is_flag_type(PeerTrustFlag) { return true; };
@ -329,6 +332,7 @@ private:
qint32 _cacheBigFileTotalTimeLimit = 0;
base::flat_map<PeerId, base::flags<PeerTrustFlag>> _trustedPeers;
base::flat_map<PeerId, int> _trustedPayPerMessage;
bool _trustedPeersRead = false;
bool _readingUserSettings = false;
bool _recentHashtagsAndBotsWereRead = false;