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::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday(); case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave(); case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
} }
Unexpected("Key in Api::UserPrivacy::KetToTL."); 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_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave: case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave; case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
case mtpc_privacyKeyNoPaidMessages:
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
} }
return std::nullopt; return std::nullopt;
} }

View file

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

View file

@ -523,6 +523,7 @@ void ApiWrap::sendMessageFail(
uint64 randomId, uint64 randomId,
FullMsgId itemId) { FullMsgId itemId) {
const auto show = ShowForPeer(peer); const auto show = ShowForPeer(peer);
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
if (show && error == u"PEER_FLOOD"_q) { if (show && error == u"PEER_FLOOD"_q) {
show->showBox( show->showBox(
Ui::MakeInformBox( Ui::MakeInformBox(
@ -577,11 +578,19 @@ void ApiWrap::sendMessageFail(
if (show) { if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now)); 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) { if (show) {
show->showToast( show->showToast(
u"Payment requirements changed. Please, try again."_q); 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)) { if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0); Assert(randomId != 0);

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h" #include "settings/settings_privacy_security.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -561,6 +562,39 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
return result; 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 } // namespace
bool EditPrivacyController::hasOption(Option option) const { bool EditPrivacyController::hasOption(Option option) const {
@ -923,11 +957,12 @@ void EditMessagesPrivacyBox(
constexpr auto kOptionPremium = 1; constexpr auto kOptionPremium = 1;
constexpr auto kOptionCharge = 2; constexpr auto kOptionCharge = 2;
const auto session = &controller->session();
const auto allowed = [=] { const auto allowed = [=] {
return controller->session().premium() return session->premium()
|| controller->session().appConfig().newRequirePremiumFree(); || session->appConfig().newRequirePremiumFree();
}; };
const auto privacy = &controller->session().api().globalPrivacy(); const auto privacy = &session->api().globalPrivacy();
const auto inner = box->verticalLayout(); const auto inner = box->verticalLayout();
inner->add(object_ptr<Ui::PlainShadow>(box)); inner->add(object_ptr<Ui::PlainShadow>(box));
@ -995,18 +1030,45 @@ void EditMessagesPrivacyBox(
state->stars = SetupChargeSlider( state->stars = SetupChargeSlider(
chargeInner, chargeInner,
controller->session().user(), session->user(),
savedValue); savedValue);
Ui::AddSkip(chargeInner); Ui::AddSkip(chargeInner);
Ui::AddSubsectionTitle( Ui::AddSubsectionTitle(
chargeInner, chargeInner,
tr::lng_messages_privacy_exceptions()); 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( const auto exceptions = Settings::AddButtonWithLabel(
chargeInner, chargeInner,
tr::lng_messages_privacy_remove_fee(), tr::lng_messages_privacy_remove_fee(),
rpl::single(u""_q), std::move(label),
st::settingsButtonNoIcon); 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::AddSkip(chargeInner);
Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about()); Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about());

View file

@ -869,15 +869,11 @@ int ChannelData::starsPerMessage() const {
} }
void ChannelData::setStarsPerMessage(int stars) { void ChannelData::setStarsPerMessage(int stars) {
if (!mgInfo || starsPerMessage() == stars) { if (mgInfo && starsPerMessage() != stars) {
return; mgInfo->_starsPerMessage = stars;
} session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
const auto removed = mgInfo->_starsPerMessage && !stars;
mgInfo->_starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
if (removed) {
session().local().clearPeerTrusted(id);
} }
checkTrustedPayForMessage();
} }
int ChannelData::peerGiftsCount() const { 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/view/history_view_element.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "storage/file_download.h" #include "storage/file_download.h"
#include "storage/storage_account.h"
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
@ -341,6 +342,17 @@ void PeerData::invalidateEmptyUserpic() {
_userpicEmpty = nullptr; _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() { ClickHandlerPtr PeerData::createOpenLink() {
return std::make_shared<PeerClickHandler>(this); return std::make_shared<PeerClickHandler>(this);
} }

View file

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

View file

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

View file

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

View file

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

View file

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