Update API scheme, improve service messages.

This commit is contained in:
John Preston 2025-02-20 12:22:20 +04:00
parent e302f328f7
commit 3633c19208
15 changed files with 253 additions and 155 deletions

View file

@ -2166,12 +2166,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
"lng_action_payment_refunded" = "{peer} refunded {amount}";
"lng_action_paid_message_sent#one" = "You paid {count} Star to send a message";
"lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message";
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}";
"lng_action_paid_message_sent#other" = "You paid {count} Star to {action}";
"lng_action_paid_message_group#one" = "{from} paid {count} Star to {action}";
"lng_action_paid_message_group#other" = "{from} paid {count} Star to {action}";
"lng_action_paid_message_one" = "send a message";
"lng_action_paid_message_some#one" = "send {count} message";
"lng_action_paid_message_some#other" = "send {count} messages";
"lng_action_paid_message_got#one" = "You received {count} Star from {name}";
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
"lng_action_paid_message_group#one" = "{from} paid {count} Star to send a message";
"lng_action_paid_message_group#other" = "{from} paid {count} Stars to send a message";
"lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all";

View file

@ -634,7 +634,9 @@ void SendConfirmedFile(
.replyTo = file->to.replyTo,
.date = NewMessageDate(file->to.options),
.shortcutId = file->to.options.shortcutId,
.starsPaid = file->to.options.starsApproved,
.starsPaid = std::min(
history->peer->starsPerMessageChecked(),
file->to.options.starsApproved),
.postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId,
.effectId = file->to.options.effectId,

View file

@ -4481,7 +4481,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
const auto replyTo = sample->replyTo();
const auto sendAs = album->options.sendAs;
const auto starsPaid = std::min(
history->peer->starsPerMessageChecked(),
history->peer->starsPerMessageChecked() * int(medias.size()),
album->options.starsApproved);
if (starsPaid) {
album->options.starsApproved -= starsPaid;

View file

@ -17,14 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_credits_graphics.h"
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "window/window_session_controller.h"
#include "styles/style_widgets.h"
@ -431,85 +428,4 @@ void ShowSendErrorToast(
});
}
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed) {
return ShowSendPaidConfirm(navigation->uiShow(), peer, error, confirmed);
}
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Data::SendError error,
Fn<void()> confirmed) {
const auto check = [=] {
const auto required = error.paidStars;
if (!required) {
return;
}
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
confirmed();
}
};
Settings::MaybeRequestBalanceIncrease(
show,
required,
Settings::SmallBalanceForMessage{ .recipientId = peer->id },
done);
};
const auto session = &peer->session();
if (session->local().isPeerTrustedPayForMessage(peer->id)) {
check();
return;
}
const auto messages = error.paidMessages;
const auto stars = error.paidStars;
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto trust = std::make_shared<QPointer<Ui::Checkbox>>();
const auto proceed = [=](Fn<void()> close) {
if ((*trust)->checked()) {
session->local().markPeerTrustedPayForMessage(peer->id);
}
check();
close();
};
Ui::ConfirmBox(box, {
.text = tr::lng_payment_confirm_text(
tr::now,
lt_count,
stars / messages,
lt_name,
Ui::Text::Bold(peer->shortName()),
Ui::Text::RichLangValue).append(' ').append(
tr::lng_payment_confirm_sure(
tr::now,
lt_count,
messages,
lt_amount,
tr::lng_payment_confirm_amount(
tr::now,
lt_count,
stars,
Ui::Text::RichLangValue),
Ui::Text::RichLangValue)),
.confirmed = proceed,
.confirmText = tr::lng_payment_confirm_button(
lt_count,
rpl::single(messages * 1.)),
.title = tr::lng_payment_confirm_title(),
});
const auto skip = st::defaultCheckbox.margin.top();
*trust = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_confirm_dont_ask(tr::now)),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}));
}
} // namespace Data

View file

@ -244,15 +244,4 @@ void ShowSendErrorToast(
not_null<PeerData*> peer,
SendError error);
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed);
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed);
} // namespace Data

View file

@ -38,10 +38,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "core/application.h"
#include "core/click_handler_types.h" // ClickHandlerContext.
#include "settings/settings_credits_graphics.h"
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "ui/item_text_options.h"
#include "lang/lang_keys.h"
@ -242,6 +245,91 @@ object_ptr<Ui::BoxContent> MakeSendErrorBox(
});
}
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendPaymentDetails details,
Fn<void()> confirmed) {
return ShowSendPaidConfirm(
navigation->uiShow(),
peer,
details,
confirmed);
}
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
SendPaymentDetails details,
Fn<void()> confirmed) {
const auto check = [=] {
const auto required = details.stars;
if (!required) {
return;
}
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
confirmed();
}
};
Settings::MaybeRequestBalanceIncrease(
show,
required,
Settings::SmallBalanceForMessage{ .recipientId = peer->id },
done);
};
const auto session = &peer->session();
if (session->local().isPeerTrustedPayForMessage(peer->id)) {
check();
return;
}
const auto messages = details.messages;
const auto stars = details.stars;
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto trust = std::make_shared<QPointer<Ui::Checkbox>>();
const auto proceed = [=](Fn<void()> close) {
if ((*trust)->checked()) {
session->local().markPeerTrustedPayForMessage(peer->id);
}
check();
close();
};
Ui::ConfirmBox(box, {
.text = tr::lng_payment_confirm_text(
tr::now,
lt_count,
stars / messages,
lt_name,
Ui::Text::Bold(peer->shortName()),
Ui::Text::RichLangValue).append(' ').append(
tr::lng_payment_confirm_sure(
tr::now,
lt_count,
messages,
lt_amount,
tr::lng_payment_confirm_amount(
tr::now,
lt_count,
stars,
Ui::Text::RichLangValue),
Ui::Text::RichLangValue)),
.confirmed = proceed,
.confirmText = tr::lng_payment_confirm_button(
lt_count,
rpl::single(messages * 1.)),
.title = tr::lng_payment_confirm_title(),
});
const auto skip = st::defaultCheckbox.margin.top();
*trust = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_confirm_dont_ask(tr::now)),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}));
}
void RequestDependentMessageItem(
not_null<HistoryItem*> item,
PeerId peerId,

View file

@ -16,6 +16,10 @@ struct SendOptions;
struct SendAction;
} // namespace Api
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
class Story;
class Thread;
@ -31,6 +35,10 @@ namespace Ui {
class BoxContent;
} // namespace Ui
namespace Window {
class SessionNavigation;
} // namespace Window
struct PreparedServiceText {
TextWithEntities text;
std::vector<ClickHandlerPtr> links;
@ -135,6 +143,17 @@ struct SendPaymentDetails {
not_null<PeerData*> peer,
int messagesCount);
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendPaymentDetails details,
Fn<void()> confirmed);
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
SendPaymentDetails details,
Fn<void()> confirmed);
[[nodiscard]] Data::SendErrorWithThread GetErrorForSending(
const std::vector<not_null<Data::Thread*>> &threads,
SendingErrorRequest request);

View file

@ -225,6 +225,14 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
} // namespace
struct HistoryWidget::SendingFiles {
Ui::PreparedList list;
Ui::SendFilesWay way;
TextWithTags caption;
Api::SendOptions options;
bool ctrlShiftEnter = false;
};
HistoryWidget::HistoryWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
@ -5896,8 +5904,7 @@ bool HistoryWidget::showSendMessageError(
const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend,
int starsApproved,
bool mediaMessage) {
int starsApproved) {
if (!_canSendMessages) {
return false;
}
@ -5908,8 +5915,7 @@ bool HistoryWidget::showSendMessageError(
.text = &textWithTags,
.ignoreSlowmodeCountdown = ignoreSlowmodeCountdown,
};
request.messagesCount = ComputeSendingMessagesCount(_history, request)
+ (mediaMessage ? 1 : 0);
request.messagesCount = ComputeSendingMessagesCount(_history, request);
const auto error = GetErrorForSending(_peer, request);
if (error) {
Data::ShowSendErrorToast(controller(), _peer, error);
@ -5926,14 +5932,14 @@ bool HistoryWidget::checkSendPayment(
const auto details = ComputePaymentDetails(_peer, messagesCount);
if (!details) {
_resendOnFullUpdated = [=] { resend(starsApproved); };
return true;
} else if (const auto stars = details->stars) {
Data::ShowSendPaidConfirm(controller(), _peer, error, [=] {
return false;
} else if (const auto stars = details->stars; stars > starsApproved) {
ShowSendPaidConfirm(controller(), _peer, *details, [=] {
resend(stars);
});
return true;
return false;
}
return false;
return true;
}
bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
@ -6019,28 +6025,53 @@ bool HistoryWidget::confirmSendingFiles(
}
void HistoryWidget::sendingFilesConfirmed(
Ui::PreparedList &&list,
Ui::SendFilesWay way,
TextWithTags &&caption,
Api::SendOptions options,
bool ctrlShiftEnter) {
Ui::PreparedList &&list,
Ui::SendFilesWay way,
TextWithTags &&caption,
Api::SendOptions options,
bool ctrlShiftEnter) {
Expects(list.filesToProcess.empty());
const auto compress = way.sendImagesAsPhotos();
if (showSendingFilesError(list, compress)) {
return;
}
sendingFilesConfirmed(std::make_shared<SendingFiles>(SendingFiles{
.list = std::move(list),
.way = way,
.caption = std::move(caption),
.options = options,
.ctrlShiftEnter = ctrlShiftEnter,
}));
}
void HistoryWidget::sendingFilesConfirmed(
std::shared_ptr<SendingFiles> args) {
const auto withPaymentApproved = [=](int approved) {
args->options.starsApproved = approved;
sendingFilesConfirmed(args);
};
const auto checked = checkSendPayment(
args->list.files.size(),
args->options.starsApproved,
withPaymentApproved);
if (!checked) {
return;
}
auto groups = DivideByGroups(
std::move(list),
way,
std::move(args->list),
args->way,
_peer->slowmodeApplied());
const auto compress = args->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(options);
auto action = prepareSendAction(args->options);
action.clearDraft = false;
if ((groups.size() != 1 || !groups.front().sentWithCaption())
&& !caption.text.isEmpty()) {
&& !args->caption.text.isEmpty()) {
auto message = Api::MessageToSend(action);
message.textWithTags = base::take(caption);
message.textWithTags = base::take(args->caption);
session().api().sendMessage(std::move(message));
}
for (auto &group : groups) {
@ -6050,7 +6081,7 @@ void HistoryWidget::sendingFilesConfirmed(
session().api().sendFiles(
std::move(group.list),
type,
base::take(caption),
base::take(args->caption),
album,
action);
}

View file

@ -320,6 +320,7 @@ private:
using TabbedPanel = ChatHelpers::TabbedPanel;
using TabbedSelector = ChatHelpers::TabbedSelector;
using VoiceToSend = HistoryView::Controls::VoiceToSend;
struct SendingFiles;
enum ScrollChangeType {
ScrollChangeNone,
@ -472,8 +473,7 @@ private:
const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend = nullptr,
int starsApproved = 0,
bool mediaMessage = false);
int starsApproved = 0);
bool checkSendPayment(
int messagesCount,
int starsApproved,
@ -485,6 +485,7 @@ private:
TextWithTags &&caption,
Api::SendOptions options,
bool ctrlShiftEnter);
void sendingFilesConfirmed(std::shared_ptr<SendingFiles> args);
void uploadFile(const QByteArray &fileContent, SendMediaType type);
void itemRemoved(not_null<const HistoryItem*> item);

View file

@ -431,35 +431,7 @@ Message::Message(
_rightAction->second->link = ReportSponsoredClickHandler(data);
}
}
if (const auto stars = data->starsPaid()) {
auto text = PreparedServiceText{
.text = data->out()
? tr::lng_action_paid_message_sent(
tr::now,
lt_count,
stars,
Ui::Text::WithEntities)
: history()->peer->isUser()
? tr::lng_action_paid_message_got(
tr::now,
lt_count,
stars,
lt_name,
Ui::Text::Link(data->from()->shortName(), 1),
Ui::Text::WithEntities)
: tr::lng_action_paid_message_group(
tr::now,
lt_count,
stars,
lt_from,
Ui::Text::Link(data->from()->shortName(), 1),
Ui::Text::WithEntities),
};
if (!data->out()) {
text.links.push_back(data->from()->createOpenLink());
}
setServicePreMessage(std::move(text));
}
initPaidInformation();
}
Message::~Message() {
@ -470,6 +442,61 @@ Message::~Message() {
}
}
void Message::initPaidInformation() {
const auto item = data();
const auto media = this->media();
const auto mine = PaidInformation{
.messages = 1,
.stars = item->starsPaid(),
};
auto info = media ? media->paidInformation().value_or(mine) : mine;
if (!info) {
return;
}
const auto action = [&] {
return (info.messages == 1)
? tr::lng_action_paid_message_one(
tr::now,
Ui::Text::WithEntities)
: tr::lng_action_paid_message_some(
tr::now,
lt_count,
info.messages,
Ui::Text::WithEntities);
};
auto text = PreparedServiceText{
.text = item->out()
? tr::lng_action_paid_message_sent(
tr::now,
lt_count,
info.stars,
lt_action,
action(),
Ui::Text::WithEntities)
: history()->peer->isUser()
? tr::lng_action_paid_message_got(
tr::now,
lt_count,
info.stars,
lt_name,
Ui::Text::Link(item->from()->shortName(), 1),
Ui::Text::WithEntities)
: tr::lng_action_paid_message_group(
tr::now,
lt_count,
info.stars,
lt_from,
Ui::Text::Link(item->from()->shortName(), 1),
lt_action,
action(),
Ui::Text::WithEntities),
};
if (!item->out()) {
text.links.push_back(item ->from()->createOpenLink());
}
setServicePreMessage(std::move(text));
}
void Message::refreshRightBadge() {
const auto item = data();
const auto text = [&] {

View file

@ -178,6 +178,7 @@ private:
bool updateBottomInfo();
void initPaidInformation();
void initLogEntryOriginal();
void initPsa();
void fromNameUpdated(int width) const;

View file

@ -78,6 +78,15 @@ enum class MediaInBubbleState : uchar {
TimeId duration,
const QString &base);
struct PaidInformation {
int messages = 0;
int stars = 0;
explicit operator bool() const {
return stars != 0;
}
};
class Media : public Object, public base::has_weak_ptr {
public:
explicit Media(not_null<Element*> parent) : _parent(parent) {
@ -121,6 +130,10 @@ public:
[[nodiscard]] virtual bool allowsFastShare() const {
return false;
}
[[nodiscard]] virtual auto paidInformation() const
-> std::optional<PaidInformation> {
return {};
}
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
}
virtual void drawHighlight(

View file

@ -867,6 +867,15 @@ QPoint GroupedMedia::resolveCustomInfoRightBottom() const {
return QPoint(width() - skipx, height() - skipy);
}
std::optional<PaidInformation> GroupedMedia::paidInformation() const {
auto result = PaidInformation();
for (const auto &part : _parts) {
++result.messages;
result.stars += part.item->starsPaid();
}
return result;
}
bool GroupedMedia::enforceBubbleWidth() const {
return _mode == Mode::Grid;
}

View file

@ -93,6 +93,7 @@ public:
bool allowsFastShare() const override {
return true;
}
std::optional<PaidInformation> paidInformation() const override;
bool customHighlight() const override {
return true;
}

View file

@ -236,7 +236,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#8555f3c2 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector<PremiumGiftOption> wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull;
userFull#d2234ea0 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
@ -1496,8 +1496,6 @@ inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = In
inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose;
inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose;
premiumGiftOption#79c059f7 flags:# months:int currency:string amount:long bot_url:flags.1?string store_product:flags.0?string = PremiumGiftOption;
paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod;
emojiStatusEmpty#2de11aae = EmojiStatus;