Fix sending invite links to paid.

This commit is contained in:
John Preston 2025-03-06 14:02:44 +04:00
parent 0ac88c0cb5
commit a1e555267e

View file

@ -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<PeerData*> peer, bool locked);
ForbiddenRow(
not_null<PeerData*> peer,
not_null<const style::PeerListItem*> 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<const style::PeerListItem*> _lockSt;
QImage _disabledFrame;
InMemoryKey _userpicKey;
int _paletteVersion = 0;
std::shared_ptr<Restriction> _restriction;
};
@ -81,6 +107,9 @@ public:
[[nodiscard]] rpl::producer<int> selectedValue() const {
return _selected.value();
}
[[nodiscard]] rpl::producer<int> starsToSend() const {
return _starsToSend.value();
}
void send(
std::vector<not_null<PeerData*>> list,
@ -89,10 +118,16 @@ public:
private:
void appendRow(not_null<UserData*> user);
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
[[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
not_null<UserData*> user) const;
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
void send(
std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> close,
Api::SendOptions options);
void setSimpleCover();
void setComplexCover();
@ -101,8 +136,11 @@ private:
const std::vector<not_null<UserData*>> &_users;
const bool _can = false;
rpl::variable<int> _selected;
rpl::variable<int> _starsToSend;
bool _sending = false;
rpl::lifetime _paymentCheckLifetime;
};
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
return _peer->session();
}
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
ForbiddenRow::ForbiddenRow(
not_null<PeerData*> peer,
not_null<const style::PeerListItem*> 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>();
}
_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<PeerListRow*> raw) {
const auto row = static_cast<ForbiddenRow*>(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<PeerListRow*> row) {
const auto checked = row->checked();
delegate()->peerListSetRowChecked(row, !checked);
_selected = _selected.current() + (checked ? -1 : 1);
const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
if (r.starsPerMessage) {
_starsToSend = _starsToSend.current()
+ (checked ? -r.starsPerMessage : r.starsPerMessage);
}
}
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> 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<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> close) {
if (_sending || list.empty()) {
send(list, show, close, {});
}
void InviteForbiddenController::send(
std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> 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<not_null<PeerData*>>();
auto waiting = base::flat_set<not_null<PeerData*>>();
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<PeerListRow> InviteForbiddenController::createRow(
std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
not_null<UserData*> user) const {
const auto locked = _can && !canInvite(user);
return std::make_unique<ForbiddenRow>(user, locked);
const auto lockSt = &computeListSt().item;
return std::make_unique<ForbiddenRow>(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();