diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index 78f863bed..e07858dda 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -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 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; } diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h index a1f66189f..676e9be11 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.h +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -32,6 +32,7 @@ public: About, Birthday, GiftsAutoSave, + NoPaidMessages, }; enum class Option { Everyone, diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1d6cb2ede..c5f407394 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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); diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index d905a5d9a..eba2b0e3a 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -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) return result; } +void EditNoPaidMessagesExceptions( + not_null window, + const Api::UserPrivacy::Rule &value) { + auto controller = std::make_unique( + &window->session(), + tr::lng_messages_privacy_remove_fee(), + value.always, + std::optional()); + auto initBox = [=, controller = controller.get()]( + not_null 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(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(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(); + 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()); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 410abacf9..f02f31d3d 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -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 { diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 45849d439..ad9449593 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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(this); } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 8d1b09289..dcec75edf 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 903ea87e5..8c334421e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -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 { diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 5c601a700..1909f4943 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -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 close) { if (singlePeer && (*trust)->checked()) { const auto session = &singlePeer->session(); - session->local().markPeerTrustedPayForMessage(recipientId); + session->local().markPeerTrustedPayForMessage( + singlePeerId, + singlePeerStars); } check(); close(); diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 1472ebf5d..ea673e503 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -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::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(); } } diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index c9e977813..86bdabde6 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -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> _trustedPeers; + base::flat_map _trustedPayPerMessage; bool _trustedPeersRead = false; bool _readingUserSettings = false; bool _recentHashtagsAndBotsWereRead = false;