From a1e555267e073ff863d642da405d3b5f2420f2be Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Mar 2025 14:02:44 +0400 Subject: [PATCH] Fix sending invite links to paid. --- .../boxes/peers/add_participants_box.cpp | 233 +++++++++++++++++- 1 file changed, 223 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index c2b6ff565..4701cac1e 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "api/api_invite_links.h" +#include "api/api_premium.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_peer_type_box.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/max_invite_box.h" +#include "chat_helpers/message_field.h" #include "lang/lang_keys.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_peer_values.h" #include "history/history.h" +#include "history/history_item_helpers.h" #include "dialogs/dialogs_indexed_list.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/show_or_premium_box.h" @@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3; class ForbiddenRow final : public PeerListRow { public: - ForbiddenRow(not_null peer, bool locked); + ForbiddenRow( + not_null peer, + not_null lockSt, + bool locked); PaintRoundImageCallback generatePaintUserpicCallback( bool forceRound) override; + Api::MessageMoneyRestriction restriction() const; + void setRestriction(Api::MessageMoneyRestriction restriction); + + void preloadUserpic() override; + void paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) override; + + bool refreshLock(); + private: + struct Restriction { + Api::MessageMoneyRestriction value; + RestrictionBadgeCache cache; + }; + const bool _locked = false; + const not_null _lockSt; QImage _disabledFrame; InMemoryKey _userpicKey; int _paletteVersion = 0; + std::shared_ptr _restriction; }; @@ -81,6 +107,9 @@ public: [[nodiscard]] rpl::producer selectedValue() const { return _selected.value(); } + [[nodiscard]] rpl::producer starsToSend() const { + return _starsToSend.value(); + } void send( std::vector> list, @@ -89,10 +118,16 @@ public: private: void appendRow(not_null user); - [[nodiscard]] std::unique_ptr createRow( + [[nodiscard]] std::unique_ptr createRow( not_null user) const; [[nodiscard]] bool canInvite(not_null peer) const; + void send( + std::vector> list, + Ui::ShowPtr show, + Fn close, + Api::SendOptions options); + void setSimpleCover(); void setComplexCover(); @@ -101,8 +136,11 @@ private: const std::vector> &_users; const bool _can = false; rpl::variable _selected; + rpl::variable _starsToSend; bool _sending = false; + rpl::lifetime _paymentCheckLifetime; + }; base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { @@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const { return _peer->session(); } -ForbiddenRow::ForbiddenRow(not_null peer, bool locked) +ForbiddenRow::ForbiddenRow( + not_null peer, + not_null lockSt, + bool locked) : PeerListRow(peer) -, _locked(locked) { +, _locked(locked) +, _lockSt(lockSt) { if (_locked) { setCustomStatus(tr::lng_invite_status_disabled(tr::now)); + } else { + setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr)); } } @@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback( }; } + +Api::MessageMoneyRestriction ForbiddenRow::restriction() const { + return _restriction + ? _restriction->value + : Api::MessageMoneyRestriction(); +} + +void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) { + if (!restriction || !restriction.starsPerMessage) { + _restriction = nullptr; + return; + } else if (!_restriction) { + _restriction = std::make_unique(); + } + _restriction->value = restriction; +} + +void ForbiddenRow::paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) { + if (const auto &r = _restriction) { + PaintRestrictionBadge( + p, + _lockSt, + r->value.starsPerMessage, + r->cache, + x, + y, + outerWidth, + st.photoSize); + } +} + +bool ForbiddenRow::refreshLock() { + if (_locked) { + return false; + } else if (const auto user = peer()->asUser()) { + using Restriction = Api::MessageMoneyRestriction; + auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr); + if (!r || !r.starsPerMessage) { + r = Restriction(); + } + if ((_restriction ? _restriction->value : Restriction()) != r) { + setRestriction(r); + return true; + } + } + return false; +} + +void ForbiddenRow::preloadUserpic() { + PeerListRow::preloadUserpic(); + + const auto peer = this->peer(); + const auto known = Api::ResolveMessageMoneyRestrictions( + peer, + nullptr).known; + if (known) { + return; + } else if (const auto user = peer->asUser()) { + const auto api = &user->session().api(); + api->premium().resolveMessageMoneyRestrictions(user); + } else if (const auto group = peer->asChannel()) { + group->updateFull(); + } +} + void InviteForbiddenController::setSimpleCover() { delegate()->peerListSetTitle( _can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant()); @@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() { } void InviteForbiddenController::prepare() { + session().api().premium().someMessageMoneyRestrictionsResolved( + ) | rpl::start_with_next([=] { + auto stars = 0; + const auto process = [&](not_null raw) { + const auto row = static_cast(raw.get()); + if (row->refreshLock()) { + delegate()->peerListUpdateRow(raw); + } + if (const auto r = row->restriction()) { + stars += r.starsPerMessage; + } + }; + auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate()->peerListRowAt(i)); + } + _starsToSend = stars; + + count = delegate()->peerListSearchRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate()->peerListSearchRowAt(i)); + } + }, lifetime()); + if (session().premium() || (_forbidden.premiumAllowsInvite.empty() && _forbidden.premiumAllowsWrite.empty())) { @@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null row) { const auto checked = row->checked(); delegate()->peerListSetRowChecked(row, !checked); _selected = _selected.current() + (checked ? -1 : 1); + const auto r = static_cast(row.get())->restriction(); + if (r.starsPerMessage) { + _starsToSend = _starsToSend.current() + + (checked ? -r.starsPerMessage : r.starsPerMessage); + } } void InviteForbiddenController::appendRow(not_null user) { @@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null user) { delegate()->peerListAppendRow(std::move(row)); if (canInvite(user)) { delegate()->peerListSetRowChecked(raw, true); + if (const auto r = raw->restriction()) { + _starsToSend = _starsToSend.current() + r.starsPerMessage; + } } } } @@ -481,7 +627,64 @@ void InviteForbiddenController::send( std::vector> list, Ui::ShowPtr show, Fn close) { - if (_sending || list.empty()) { + send(list, show, close, {}); +} + +void InviteForbiddenController::send( + std::vector> list, + Ui::ShowPtr show, + Fn close, + Api::SendOptions options) { + if (list.empty()) { + return; + } + _paymentCheckLifetime.destroy(); + + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + send(list, show, close, copy); + }; + const auto messagesCount = 1; + const auto alreadyApproved = options.starsApproved; + auto paid = std::vector>(); + auto waiting = base::flat_set>(); + auto totalStars = 0; + for (const auto &peer : list) { + const auto details = ComputePaymentDetails(peer, messagesCount); + if (!details) { + waiting.emplace(peer); + } else if (details->stars > 0) { + totalStars += details->stars; + paid.push_back(peer); + } + } + if (!waiting.empty()) { + session().changes().peerUpdates( + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + if (waiting.contains(update.peer)) { + withPaymentApproved(alreadyApproved); + } + }, _paymentCheckLifetime); + + if (!session().credits().loaded()) { + session().credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + withPaymentApproved(alreadyApproved); + }, _paymentCheckLifetime); + } + return; + } else if (totalStars > alreadyApproved) { + const auto sessionShow = Main::MakeSessionShow(show, &session()); + ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{ + .messages = messagesCount, + .stars = totalStars, + }, [=] { withPaymentApproved(totalStars); }); + return; + } else if (_sending) { return; } _sending = true; @@ -492,12 +695,18 @@ void InviteForbiddenController::send( if (link.isEmpty()) { return false; } + auto full = options; auto &api = _peer->session().api(); - auto options = Api::SendOptions(); for (const auto &to : list) { + auto copy = full; + copy.starsApproved = std::min( + to->starsPerMessageChecked(), + full.starsApproved); + full.starsApproved -= copy.starsApproved; + const auto history = to->owner().history(to); auto message = Api::MessageToSend( - Api::SendAction(history, options)); + Api::SendAction(history, copy)); message.textWithTags = { link }; message.action.clearDraft = false; api.sendMessage(std::move(message)); @@ -542,10 +751,11 @@ void InviteForbiddenController::send( } } -std::unique_ptr InviteForbiddenController::createRow( +std::unique_ptr InviteForbiddenController::createRow( not_null user) const { const auto locked = _can && !canInvite(user); - return std::make_unique(user, locked); + const auto lockSt = &computeListSt().item; + return std::make_unique(user, lockSt, locked); } } // namespace @@ -929,12 +1139,15 @@ bool ChatInviteForbidden( ) | rpl::start_with_next([=](bool has) { box->clearButtons(); if (has) { - box->addButton(tr::lng_via_link_send(), [=] { + const auto send = box->addButton(tr::lng_via_link_send(), [=] { weak->send( box->collectSelectedRows(), box->uiShow(), crl::guard(box, [=] { box->closeBox(); })); }); + send->setText(PaidSendButtonText( + weak->starsToSend(), + tr::lng_via_link_send())); } box->addButton(tr::lng_create_group_skip(), [=] { box->closeBox();