From 45c7829cd8f33d0c16daacfaf8da987b68936bb7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Feb 2025 14:12:19 +0400 Subject: [PATCH] Track stars-per-message for users and channels. --- .../SourceFiles/boxes/edit_privacy_box.cpp | 137 +++++++++++------- Telegram/SourceFiles/boxes/edit_privacy_box.h | 5 + .../boxes/peers/edit_peer_info_box.cpp | 32 +++- .../boxes/peers/edit_peer_permissions_box.cpp | 62 ++++++-- .../boxes/peers/edit_peer_permissions_box.h | 1 + Telegram/SourceFiles/data/data_changes.h | 65 +++++---- Telegram/SourceFiles/data/data_channel.cpp | 17 +++ Telegram/SourceFiles/data/data_channel.h | 5 + Telegram/SourceFiles/data/data_user.cpp | 13 ++ Telegram/SourceFiles/data/data_user.h | 4 + 10 files changed, 239 insertions(+), 102 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 818ed5c35..f13b5bae0 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -45,9 +45,9 @@ namespace { constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; constexpr auto kGetPercent = 85; -constexpr auto kStarsMin = 10; +constexpr auto kStarsMin = 1; constexpr auto kStarsMax = 9000; -constexpr auto kDefaultChargeStars = 200; +constexpr auto kDefaultChargeStars = 10; using Exceptions = Api::UserPrivacy::Exceptions; @@ -934,7 +934,9 @@ void EditMessagesPrivacyBox( Ui::AddSkip(inner, st::messagePrivacyTopSkip); Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle()); const auto group = std::make_shared( - (privacy->newRequirePremiumCurrent() + (!allowed() + ? kOptionAll + : privacy->newRequirePremiumCurrent() ? kOptionPremium : privacy->newChargeStarsCurrent() ? kOptionCharge @@ -984,66 +986,17 @@ void EditMessagesPrivacyBox( const auto chargeInner = chargeWrap->entity(); Ui::AddSkip(chargeInner); - Ui::AddSubsectionTitle(chargeInner, tr::lng_messages_privacy_price()); struct State { rpl::variable stars; }; const auto state = std::make_shared(); const auto savedValue = privacy->newChargeStarsCurrent(); - const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars; - state->stars = chargeStars; - auto values = std::vector(); - if (chargeStars < kStarsMin) { - values.push_back(chargeStars); - } - for (auto i = kStarsMin; i < 100; ++i) { - values.push_back(i); - } - for (auto i = 100; i < 1000; i += 10) { - if (i < chargeStars + 10 && chargeStars < i) { - values.push_back(chargeStars); - } - values.push_back(i); - } - for (auto i = 1000; i < kStarsMax + 1; i += 100) { - if (i < chargeStars + 100 && chargeStars < i) { - values.push_back(chargeStars); - } - values.push_back(i); - } - const auto valuesCount = int(values.size()); - const auto setStars = [=](int value) { - state->stars = value; - }; - chargeInner->add( - MakeChargeStarsSlider( - chargeInner, - &st::settingsScale, - &st::settingsScaleLabel, - valuesCount, - [=](int index) { return values[index]; }, - chargeStars, - setStars, - setStars), - st::boxRowPadding); - Ui::AddSkip(chargeInner); - - auto dollars = state->stars.value() | rpl::map([=](int stars) { - const auto ratio = controller->session().appConfig().get( - u"stars_usd_withdraw_rate_x1000"_q, - 1200); - const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); - return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); - }); - Ui::AddDividerText( + state->stars = SetupChargeSlider( chargeInner, - tr::lng_messages_privacy_price_about( - lt_percent, - rpl::single(QString::number(kGetPercent) + '%'), - lt_amount, - std::move(dollars))); + controller->session().user(), + savedValue); Ui::AddSkip(chargeInner); Ui::AddSubsectionTitle( @@ -1137,3 +1090,77 @@ void EditMessagesPrivacyBox( }); } } + +rpl::producer SetupChargeSlider( + not_null container, + not_null peer, + int savedValue) { + struct State { + rpl::variable stars; + }; + const auto group = !peer->isUser(); + const auto state = container->lifetime().make_state(); + const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars; + state->stars = chargeStars; + + Ui::AddSubsectionTitle(container, group + ? tr::lng_rights_charge_price() + : tr::lng_messages_privacy_price()); + + auto values = std::vector(); + if (chargeStars < kStarsMin) { + values.push_back(chargeStars); + } + for (auto i = kStarsMin; i < 100; ++i) { + values.push_back(i); + } + for (auto i = 100; i < 1000; i += 10) { + if (i < chargeStars + 10 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + for (auto i = 1000; i < kStarsMax + 1; i += 100) { + if (i < chargeStars + 100 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + const auto valuesCount = int(values.size()); + const auto setStars = [=](int value) { + state->stars = value; + }; + container->add( + MakeChargeStarsSlider( + container, + &st::settingsScale, + &st::settingsScaleLabel, + valuesCount, + [=](int index) { return values[index]; }, + chargeStars, + setStars, + setStars), + st::boxRowPadding); + + const auto skip = 2 * st::defaultVerticalListSkip; + Ui::AddSkip(container, skip); + + auto dollars = state->stars.value() | rpl::map([=](int stars) { + const auto ratio = peer->session().appConfig().get( + u"stars_usd_withdraw_rate_x1000"_q, + 1200); + const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); + return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); + }); + Ui::AddDividerText( + container, + (group + ? tr::lng_rights_charge_price_about + : tr::lng_messages_privacy_price_about)( + lt_percent, + rpl::single(QString::number(kGetPercent) + '%'), + lt_amount, + std::move(dollars))); + + return state->stars.value(); +} diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index bd87e90f2..256ebe5b5 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -169,3 +169,8 @@ private: void EditMessagesPrivacyBox( not_null box, not_null controller); + +[[nodiscard]] rpl::producer SetupChargeSlider( + not_null container, + not_null peer, + int savedValue); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 6101f980a..0299e7f9b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -219,6 +219,33 @@ void SaveSlowmodeSeconds( api->registerModifyRequest(key, requestId); } +void SaveStarsPerMessage( + not_null channel, + int starsPerMessage, + Fn done) { + const auto api = &channel->session().api(); + const auto key = Api::RequestKey("stars_per_message", channel->id); + + const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice( + channel->inputChannel, + MTP_long(starsPerMessage) + )).done([=](const MTPUpdates &result) { + api->clearModifyRequest(key); + api->applyUpdates(result); + channel->setStarsPerMessage(starsPerMessage); + done(); + }).fail([=](const MTP::Error &error) { + api->clearModifyRequest(key); + if (error.type() != u"CHAT_NOT_MODIFIED"_q) { + return; + } + channel->setStarsPerMessage(starsPerMessage); + done(); + }).send(); + + api->registerModifyRequest(key, requestId); +} + void SaveBoostsUnrestrict( not_null channel, int boostsUnrestrict, @@ -271,6 +298,7 @@ void ShowEditPermissions( channel, result.boostsUnrestrict, close); + SaveStarsPerMessage(channel, result.starsPerMessage, close); } }; auto done = [=](EditPeerPermissionsBoxResult result) { @@ -282,7 +310,9 @@ void ShowEditPermissions( const auto saveFor = peer->migrateToOrMe(); const auto chat = saveFor->asChat(); if (!chat - || (!result.slowmodeSeconds && !result.boostsUnrestrict)) { + || (!result.slowmodeSeconds + && !result.boostsUnrestrict + && !result.starsPerMessage)) { save(saveFor, result); return; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index e703994f5..ecb0b8e76 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_values.h" #include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_peer_info_box.h" +#include "boxes/edit_privacy_box.h" #include "settings/settings_power_saving.h" #include "window/window_session_controller.h" #include "window/window_controller.h" @@ -940,9 +941,7 @@ rpl::producer AddBoostsUnrestrictSlider( const auto boostsUnrestrict = lifetime.make_state>( channel ? channel->boostsUnrestrict() : 0); - container->add( - object_ptr(container), - { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); + Ui::AddSkip(container); auto enabled = boostsUnrestrict->value( ) | rpl::map(_1 > 0); @@ -1006,19 +1005,20 @@ rpl::producer AddBoostsUnrestrictWrapped( object_ptr>( container, object_ptr(container))); - wrap->toggleOn(rpl::duplicate(shown), anim::type::normal); + wrap->toggleOn(std::move(shown), anim::type::normal); wrap->finishAnimating(); - auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer); - const auto divider = container->add( + const auto inner = wrap->entity(); + + auto result = AddBoostsUnrestrictSlider(inner, peer); + + const auto skip = st::defaultVerticalListSkip; + const auto divider = inner->add( object_ptr>( - container, - object_ptr(container), - QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip })); - divider->toggleOn(rpl::combine( - std::move(shown), - rpl::duplicate(result), - !rpl::mappers::_1 || !rpl::mappers::_2)); + inner, + object_ptr(inner), + QMargins{ 0, skip, 0, skip })); + divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1)); divider->finishAnimating(); return result; @@ -1157,7 +1157,38 @@ void ShowEditPeerPermissionsBox( rpl::variable slowmodeSeconds; rpl::variable boostsUnrestrict; rpl::variable hasSendRestrictions; + rpl::variable starsPerMessage; }; + const auto state = inner->lifetime().make_state(); + + Ui::AddSkip(inner); + Ui::AddDivider(inner); + Ui::AddSkip(inner); + const auto starsPerMessage = peer->isChannel() + ? peer->asChannel()->starsPerMessage() + : 0; + const auto charging = inner->add(object_ptr( + inner, + tr::lng_rights_charge_stars(), + st::settingsButtonNoIcon)); + charging->toggleOn(rpl::single(starsPerMessage > 0)); + Ui::AddSkip(inner); + Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about()); + + const auto chargeWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + chargeWrap->toggleOn(charging->toggledValue()); + chargeWrap->finishAnimating(); + const auto chargeInner = chargeWrap->entity(); + + Ui::AddSkip(chargeInner); + state->starsPerMessage = SetupChargeSlider( + chargeInner, + peer, + starsPerMessage); + static constexpr auto kSendRestrictions = Flag::EmbedLinks | Flag::SendGames | Flag::SendGifs @@ -1171,7 +1202,6 @@ void ShowEditPeerPermissionsBox( | Flag::SendVoiceMessages | Flag::SendFiles | Flag::SendOther; - const auto state = inner->lifetime().make_state(); state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0) || (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0); state->boostsUnrestrict = AddBoostsUnrestrictWrapped( @@ -1212,10 +1242,14 @@ void ShowEditPeerPermissionsBox( const auto boostsUnrestrict = hasRestrictions ? state->boostsUnrestrict.current() : 0; + const auto starsPerMessage = charging->toggled() + ? state->starsPerMessage.current() + : 0; done({ restrictions, slowmodeSeconds, boostsUnrestrict, + starsPerMessage, }); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index ce45f4e68..6c4863b7b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final { ChatRestrictions rights; int slowmodeSeconds = 0; int boostsUnrestrict = 0; + int starsPerMessage = 0; }; void ShowEditPeerPermissionsBox( diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index cff431720..9e19fc617 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -75,46 +75,47 @@ struct PeerUpdate { BackgroundEmoji = (1ULL << 15), StoriesState = (1ULL << 16), VerifyInfo = (1ULL << 17), + StarsPerMessage = (1ULL << 18), // For users - CanShareContact = (1ULL << 18), - IsContact = (1ULL << 19), - PhoneNumber = (1ULL << 20), - OnlineStatus = (1ULL << 21), - BotCommands = (1ULL << 22), - BotCanBeInvited = (1ULL << 23), - BotStartToken = (1ULL << 24), - CommonChats = (1ULL << 25), - PeerGifts = (1ULL << 26), - HasCalls = (1ULL << 27), - SupportInfo = (1ULL << 28), - IsBot = (1ULL << 29), - EmojiStatus = (1ULL << 30), - BusinessDetails = (1ULL << 31), - Birthday = (1ULL << 32), - PersonalChannel = (1ULL << 33), - StarRefProgram = (1ULL << 34), + CanShareContact = (1ULL << 19), + IsContact = (1ULL << 20), + PhoneNumber = (1ULL << 21), + OnlineStatus = (1ULL << 22), + BotCommands = (1ULL << 23), + BotCanBeInvited = (1ULL << 24), + BotStartToken = (1ULL << 25), + CommonChats = (1ULL << 26), + PeerGifts = (1ULL << 27), + HasCalls = (1ULL << 28), + SupportInfo = (1ULL << 29), + IsBot = (1ULL << 30), + EmojiStatus = (1ULL << 31), + BusinessDetails = (1ULL << 32), + Birthday = (1ULL << 33), + PersonalChannel = (1ULL << 34), + StarRefProgram = (1ULL << 35), // For chats and channels - InviteLinks = (1ULL << 35), - Members = (1ULL << 36), - Admins = (1ULL << 37), - BannedUsers = (1ULL << 38), - Rights = (1ULL << 39), - PendingRequests = (1ULL << 40), - Reactions = (1ULL << 41), + InviteLinks = (1ULL << 36), + Members = (1ULL << 37), + Admins = (1ULL << 38), + BannedUsers = (1ULL << 39), + Rights = (1ULL << 40), + PendingRequests = (1ULL << 41), + Reactions = (1ULL << 42), // For channels - ChannelAmIn = (1ULL << 42), - StickersSet = (1ULL << 43), - EmojiSet = (1ULL << 44), - ChannelLinkedChat = (1ULL << 45), - ChannelLocation = (1ULL << 46), - Slowmode = (1ULL << 47), - GroupCall = (1ULL << 48), + ChannelAmIn = (1ULL << 43), + StickersSet = (1ULL << 44), + EmojiSet = (1ULL << 45), + ChannelLinkedChat = (1ULL << 46), + ChannelLocation = (1ULL << 47), + Slowmode = (1ULL << 48), + GroupCall = (1ULL << 49), // For iteration - LastUsedBit = (1ULL << 48), + LastUsedBit = (1ULL << 49), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 79b9fe4e6..3d10c3579 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -859,6 +859,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) { session().changes().peerUpdated(this, UpdateFlag::Slowmode); } +int ChannelData::starsPerMessage() const { + if (const auto info = mgInfo.get()) { + return info->starsPerMessage; + } + return 0; +} + +void ChannelData::setStarsPerMessage(int stars) { + if (!mgInfo || starsPerMessage() == stars) { + return; + } + mgInfo->starsPerMessage = stars; + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); +} + int ChannelData::peerGiftsCount() const { return _peerGiftsCount; } @@ -1209,6 +1224,8 @@ void ApplyChannelUpdate( } else { channel->setLinkedChat(nullptr); } + channel->setStarsPerMessage( + update.vsend_paid_messages_stars().value_or_empty()); if (const auto history = channel->owner().historyLoaded(channel)) { if (const auto available = update.vavailable_min_id()) { history->clearUpTill(available->v); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 620cca9d1..c49ada9ac 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -145,6 +145,8 @@ public: int slowmodeSeconds = 0; TimeId slowmodeLastMessage = 0; + int starsPerMessage = 0; + private: ChatData *_migratedFrom = nullptr; ChannelLocation _location; @@ -456,6 +458,9 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); + [[nodiscard]] int starsPerMessage() const; + void setStarsPerMessage(int stars); + [[nodiscard]] int peerGiftsCount() const; void setPeerGiftsCount(int count); diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 0c36d9945..ac761e7ae 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -532,6 +532,17 @@ bool UserData::readDatesPrivate() const { return (flags() & UserDataFlag::ReadDatesPrivate); } +int UserData::starsPerMessage() const { + return _starsPerMessage; +} + +void UserData::setStarsPerMessage(int stars) { + if (_starsPerMessage != stars) { + _starsPerMessage = stars; + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + bool UserData::canAddContact() const { return canShareThisContact() && !isContact(); } @@ -722,6 +733,8 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setTranslationDisabled(update.is_translations_disabled()); user->setPrivateForwardName( update.vprivate_forward_name().value_or_empty()); + user->setStarsPerMessage( + update.vsend_paid_messages_stars().value_or_empty()); if (const auto info = user->botInfo.get()) { const auto group = update.vbot_group_admin_rights() diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index c7a200e33..637c6a170 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -179,6 +179,9 @@ public: [[nodiscard]] bool canSendIgnoreRequirePremium() const; [[nodiscard]] bool readDatesPrivate() const; + [[nodiscard]] int starsPerMessage() const; + void setStarsPerMessage(int stars); + [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -268,6 +271,7 @@ private: Data::Birthday _birthday; int _commonChatsCount = 0; int _peerGiftsCount = 0; + int _starsPerMessage = 0; ContactStatus _contactStatus = ContactStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown;