From bbc14ba74fc6e6c8d09e916515993de2a6ff3beb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Feb 2025 19:03:11 +0400 Subject: [PATCH] Allow sending paid messages. --- Telegram/Resources/langs/lang.strings | 7 +- Telegram/SourceFiles/api/api_common.h | 1 - Telegram/SourceFiles/api/api_polls.cpp | 5 +- Telegram/SourceFiles/api/api_sending.cpp | 15 +- Telegram/SourceFiles/apiwrap.cpp | 26 +-- Telegram/SourceFiles/boxes/share_box.cpp | 7 +- .../chat_helpers/chat_helpers.style | 4 + .../SourceFiles/core/click_handler_types.cpp | 4 +- Telegram/SourceFiles/data/data_channel.cpp | 52 +++++- Telegram/SourceFiles/data/data_channel.h | 14 +- Telegram/SourceFiles/data/data_peer.cpp | 43 +++++ Telegram/SourceFiles/data/data_peer.h | 6 + Telegram/SourceFiles/data/data_user.cpp | 37 ++++ Telegram/SourceFiles/data/data_user.h | 8 +- Telegram/SourceFiles/history/history_item.cpp | 18 +- .../SourceFiles/history/history_widget.cpp | 155 ++++++++++++++-- Telegram/SourceFiles/history/history_widget.h | 5 + .../view/history_view_replies_section.h | 1 + .../inline_bots/bot_attach_web_view.cpp | 8 +- .../media/stories/media_stories_share.cpp | 5 +- .../SourceFiles/payments/payments_form.cpp | 4 +- .../settings/settings_credits_graphics.cpp | 7 + .../settings/settings_credits_graphics.h | 6 +- .../SourceFiles/storage/storage_account.cpp | 172 ++++++++++-------- .../SourceFiles/storage/storage_account.h | 30 +-- 25 files changed, 497 insertions(+), 143 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index be5e8e8b6..95da224c6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2789,6 +2789,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts."; "lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels."; "lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts."; +"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_enough" = "You have enough stars at the moment. {link}"; @@ -4821,8 +4822,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_slowmode_seconds#one" = "{count} second"; "lng_slowmode_seconds#other" = "{count} seconds"; -"lng_payment_for_message#one" = "Pay {star}{count} for 1 Message"; -"lng_payment_for_message#other" = "Pay {star}{count} for 1 Message"; +"lng_payment_for_message" = "Pay {cost} for 1 Message"; "lng_payment_confirm_title" = "Confirm payment"; "lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; "lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; @@ -4830,8 +4830,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payment_confirm_sure#other" = "Would you like to pay **{count}** Stars to send one message?"; "lng_payment_confirm_dont_ask" = "Don't ask me again"; "lng_payment_confirm_button" = "Pay for 1 Message"; -"lng_payment_bar_text#one" = "{name} must pay {star}{count} for each message to you."; -"lng_payment_bar_text#other" = "{name} must pay {star}{count} for each message to you."; +"lng_payment_bar_text" = "{name} must pay {cost} for each message to you."; "lng_payment_bar_button" = "Remove Fee"; "lng_payment_refund_title" = "Remove Fee"; "lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 5a8347e86..77c30d095 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -21,7 +21,6 @@ inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE); struct SendOptions { uint64 price = 0; - int64 paidByStars = 0; PeerData *sendAs = nullptr; TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index e1a63c853..aa03ee94d 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -59,6 +59,7 @@ void Polls::create( history->startSavingCloudDraft(topicRootId); } const auto silentPost = ShouldSendSilent(peer, action.options); + const auto starsPaid = peer->commitStarsForMessage(); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } @@ -71,7 +72,7 @@ void Polls::create( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } const auto sendAs = action.options.sendAs; @@ -97,7 +98,7 @@ void Polls::create( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 124ba63f9..319032aed 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -95,6 +95,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name() : QString(); + const auto starsPaid = peer->commitStarsForMessage(); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -111,7 +112,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -133,7 +134,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId); @@ -194,6 +195,7 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } const auto captionText = caption.text; + const auto starsPaid = peer->commitStarsForMessage(); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -210,7 +212,7 @@ void SendExistingMedia( flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -248,7 +250,7 @@ void SendExistingMedia( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { if (error.code() == 400 @@ -388,7 +390,8 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -423,7 +426,7 @@ bool SendDice(MessageToSend &message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId, newId); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a56722212..4362f704b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3883,7 +3883,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_effect; mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars; mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -3943,7 +3944,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), done, fail); } else { histories.sendPreparedMessage( @@ -3962,7 +3963,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), done, fail); } isFirst = false; @@ -4059,7 +4060,8 @@ void ApiWrap::sendInlineResult( if (action.options.hideViaBot) { sendFlags |= SendFlag::f_hide_via; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= SendFlag::f_allow_paid_stars; } @@ -4100,7 +4102,7 @@ void ApiWrap::sendInlineResult( MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { history->finishSavingCloudDraft( topicRootId, @@ -4232,6 +4234,7 @@ void ApiWrap::sendMediaWithRandomId( Fn done) { const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4241,6 +4244,7 @@ void ApiWrap::sendMediaWithRandomId( Api::ConvertOption::SkipLocal); const auto updateRecentStickers = Api::HasAttachedStickers(media); + const auto starsPaid = peer->commitStarsForMessage(); using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4254,10 +4258,9 @@ void ApiWrap::sendMediaWithRandomId( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) | (options.invertCaption ? Flag::f_invert_media : Flag(0)) - | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); histories.sendPreparedMessage( history, @@ -4282,7 +4285,7 @@ void ApiWrap::sendMediaWithRandomId( (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { @@ -4311,6 +4314,7 @@ void ApiWrap::sendMultiPaidMedia( const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4318,6 +4322,7 @@ void ApiWrap::sendMultiPaidMedia( _session, caption.entities, Api::ConvertOption::SkipLocal); + const auto starsPaid = peer->commitStarsForMessage(); using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4331,10 +4336,9 @@ void ApiWrap::sendMultiPaidMedia( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) | (options.invertCaption ? Flag::f_invert_media : Flag(0)) - | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); album->sent = true; histories.sendPreparedMessage( @@ -4358,7 +4362,7 @@ void ApiWrap::sendMultiPaidMedia( (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (const auto album = _sendingAlbums.take(groupId)) { const auto copy = (*album)->items; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index fb6822f5d..c88a39f6b 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1571,6 +1571,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( : topicRootId; const auto peer = thread->peer(); const auto threadHistory = thread->owningHistory(); + const auto starsPaid = peer->commitStarsForMessage(); histories.sendRequest(threadHistory, requestType, [=]( Fn finish) { const auto session = &threadHistory->session(); @@ -1583,9 +1584,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) - | (options.paidByStars - ? Flag::f_allow_paid_stars - : Flag()); + | (starsPaid ? Flag::f_allow_paid_stars : Flag()); threadHistory->sendRequestId = api.request( MTPmessages_ForwardMessages( MTP_flags(sendFlags), @@ -1598,7 +1597,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( MTP_inputPeerEmpty(), // send_as Data::ShortcutIdToMTP(session, options.shortcutId), MTP_int(videoTimestamp.value_or(0)), - MTP_long(options.paidByStars) + MTP_long(starsPaid) )).done([=](const MTPUpdates &updates, mtpRequestId reqId) { threadHistory->session().api().applyUpdates(updates); state->requests.remove(reqId); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9d13bb099..a380b6d5f 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -855,6 +855,10 @@ historyComposeButton: FlatButton { color: historyComposeButtonBgRipple; } } +historyComposeButtonText: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; + textFg: windowActiveTextFg; +} historyGiftToChannel: IconButton(defaultIconButton) { width: 46px; height: 46px; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index ded39a2f8..9e290988f 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -205,13 +205,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const { }); }; if (_bot->isVerified() - || _bot->session().local().isBotTrustedOpenGame(_bot->id)) { + || _bot->session().local().isPeerTrustedOpenGame(_bot->id)) { openGame(); } else { if (const auto controller = my.sessionWindow.get()) { const auto callback = [=, bot = _bot](Fn close) { close(); - bot->session().local().markBotTrustedOpenGame(bot->id); + bot->session().local().markPeerTrustedOpenGame(bot->id); openGame(); }; controller->show(Ui::MakeConfirmBox({ diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 3d10c3579..909823794 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "api/api_global_privacy.h" +#include "data/components/credits.h" #include "data/data_changes.h" #include "data/data_channel_admins.h" #include "data/data_user.h" @@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "api/api_invite_links.h" #include "apiwrap.h" +#include "storage/storage_account.h" #include "ui/unread_badge.h" #include "window/notifications_manager.h" @@ -861,7 +863,7 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) { int ChannelData::starsPerMessage() const { if (const auto info = mgInfo.get()) { - return info->starsPerMessage; + return info->_starsPerMessage; } return 0; } @@ -870,8 +872,54 @@ void ChannelData::setStarsPerMessage(int stars) { if (!mgInfo || starsPerMessage() == stars) { return; } - mgInfo->starsPerMessage = stars; + const auto removed = mgInfo->_starsPerMessage && !stars; + mgInfo->_starsPerMessage = stars; session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + if (removed) { + session().local().clearPeerTrusted(id); + } +} + +int ChannelData::starsForMessageLocked() const { + if (const auto info = mgInfo.get()) { + return info->_starsForMessageLocked; + } + return 0; +} + +void ChannelData::lockStarsForMessage() { + const auto info = mgInfo.get(); + if (!info || info->_starsForMessageLocked == info->_starsPerMessage) { + return; + } + cancelStarsForMessage(); + if (info->_starsPerMessage) { + info->_starsForMessageLocked = info->_starsPerMessage; + session().credits().lock(StarsAmount(info->_starsPerMessage)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + +int ChannelData::commitStarsForMessage() { + const auto info = mgInfo.get(); + if (!info) { + return 0; + } else if (const auto stars = base::take(info->_starsForMessageLocked)) { + session().credits().withdrawLocked(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + return stars; + } + return 0; +} + +void ChannelData::cancelStarsForMessage() { + const auto info = mgInfo.get(); + if (!info) { + return; + } else if (const auto stars = base::take(info->_starsForMessageLocked)) { + session().credits().unlock(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } } int ChannelData::peerGiftsCount() const { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index c49ada9ac..9484f8974 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_bot_commands.h" #include "data/data_user_names.h" +class ChannelData; + struct ChannelLocation { QString address; Data::LocationPoint point; @@ -145,13 +147,15 @@ public: int slowmodeSeconds = 0; TimeId slowmodeLastMessage = 0; - int starsPerMessage = 0; - private: ChatData *_migratedFrom = nullptr; ChannelLocation _location; Data::ChatBotCommands _botCommands; std::unique_ptr _forum; + int _starsPerMessage = 0; + int _starsForMessageLocked = 0; + + friend class ChannelData; }; @@ -458,8 +462,12 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); - [[nodiscard]] int starsPerMessage() const; void setStarsPerMessage(int stars); + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); [[nodiscard]] int peerGiftsCount() const; void setPeerGiftsCount(int count); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index b26234ee6..05e07ef26 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1450,6 +1450,49 @@ bool PeerData::canManageGroupCall() const { return false; } +int PeerData::starsPerMessage() const { + if (const auto user = asUser()) { + return user->starsPerMessage(); + } else if (const auto channel = asChannel()) { + return channel->starsPerMessage(); + } + return 0; +} + +int PeerData::starsForMessageLocked() const { + if (const auto user = asUser()) { + return user->starsForMessageLocked(); + } else if (const auto channel = asChannel()) { + return channel->starsForMessageLocked(); + } + return 0; +} + +void PeerData::lockStarsForMessage() { + if (const auto user = asUser()) { + user->lockStarsForMessage(); + } else if (const auto channel = asChannel()) { + channel->lockStarsForMessage(); + } +} + +int PeerData::commitStarsForMessage() { + if (const auto user = asUser()) { + return user->commitStarsForMessage(); + } else if (const auto channel = asChannel()) { + return channel->commitStarsForMessage(); + } + return 0; +} + +void PeerData::cancelStarsForMessage() { + if (const auto user = asUser()) { + user->cancelStarsForMessage(); + } else if (const auto channel = asChannel()) { + channel->cancelStarsForMessage(); + } +} + Data::GroupCall *PeerData::groupCall() const { if (const auto chat = asChat()) { return chat->groupCall(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 89d5e7d18..b67f3b5b5 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -268,6 +268,12 @@ public: [[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] bool canManageGroupCall() const; + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); + [[nodiscard]] UserData *asBot(); [[nodiscard]] const UserData *asBot() const; [[nodiscard]] UserData *asUser(); diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index ac761e7ae..af333ed6e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sensitive_content.h" #include "api/api_statistics.h" #include "storage/localstorage.h" +#include "storage/storage_account.h" #include "storage/storage_user_photos.h" #include "main/main_session.h" #include "data/business/data_business_common.h" @@ -538,8 +539,44 @@ 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); + } + } +} + +int UserData::starsForMessageLocked() const { + return _starsForMessageLocked; +} + +void UserData::lockStarsForMessage() { + if (_starsPerMessage == _starsForMessageLocked) { + return; + } + cancelStarsForMessage(); + if (_starsPerMessage) { + _starsForMessageLocked = _starsPerMessage; + session().credits().lock(StarsAmount(_starsPerMessage)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + +int UserData::commitStarsForMessage() { + if (const auto stars = base::take(_starsForMessageLocked)) { + session().credits().withdrawLocked(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + return stars; + } + return 0; +} + +void UserData::cancelStarsForMessage() { + if (const auto stars = base::take(_starsForMessageLocked)) { + session().credits().unlock(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); } } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 637c6a170..54c647033 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "core/stars_amount.h" +#include "data/components/credits.h" #include "data/data_birthday.h" #include "data/data_peer.h" #include "data/data_chat_participant_status.h" @@ -179,8 +180,12 @@ public: [[nodiscard]] bool canSendIgnoreRequirePremium() const; [[nodiscard]] bool readDatesPrivate() const; - [[nodiscard]] int starsPerMessage() const; void setStarsPerMessage(int stars); + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -272,6 +277,7 @@ private: int _commonChatsCount = 0; int _peerGiftsCount = 0; int _starsPerMessage = 0; + int _starsForMessageLocked = 0; ContactStatus _contactStatus = ContactStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cd2e5b724..406ae555a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5577,7 +5577,23 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto preparePaidMessage = [&]( const MTPDmessageActionPaidMessage &action) { auto result = PreparedServiceText(); - result.text.text = u"paid for message"_q; AssertIsDebug(); + const auto stars = action.vstars().v; + if (_from->isSelf()) { + result.text = tr::lng_action_paid_message_sent( + tr::now, + lt_count, + stars, + Ui::Text::WithEntities); + } else { + result.links.push_back(_from->createOpenLink()); + result.text = tr::lng_action_paid_message_got( + tr::now, + lt_count, + stars, + lt_name, + Ui::Text::Link(_from->shortName(), 1), + Ui::Text::WithEntities); + } return result; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 88060ecf4..bb8da7dfd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_unread_things.h" #include "ui/boxes/confirm_box.h" #include "boxes/delete_messages_box.h" +#include "boxes/send_credits_box.h" #include "boxes/send_files_box.h" #include "boxes/share_box.h" #include "boxes/edit_caption_box.h" @@ -129,6 +130,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "lang/lang_keys.h" #include "settings/business/settings_quick_replies.h" +#include "settings/settings_credits_graphics.h" #include "storage/localimageloader.h" #include "storage/storage_account.h" #include "storage/file_upload.h" @@ -146,6 +148,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/chat/continuous_scroll.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" @@ -263,6 +266,7 @@ HistoryWidget::HistoryWidget( tr::lng_channel_mute(tr::now).toUpper(), st::historyComposeButton) , _reportMessages(this, QString(), st::historyComposeButton) +, _payForMessage(this, QString(), st::historyComposeButton) , _attachToggle(this, st::historyAttach) , _tabbedSelectorToggle(this, st::historyAttachEmoji) , _botKeyboardShow(this, st::historyBotKeyboardShow) @@ -380,6 +384,7 @@ HistoryWidget::HistoryWidget( _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); setupGiftToChannelButton(); _reportMessages->addClickHandler([=] { reportSelectedMessages(); }); + _payForMessage->addClickHandler([=] { payForMessage(); }); _field->submits( ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { sendWithModifiers(modifiers); @@ -520,6 +525,7 @@ HistoryWidget::HistoryWidget( _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); initVoiceRecordBar(); @@ -798,6 +804,7 @@ HistoryWidget::HistoryWidget( | PeerUpdateFlag::MessagesTTL | PeerUpdateFlag::ChatThemeEmoji | PeerUpdateFlag::FullInfo + | PeerUpdateFlag::StarsPerMessage ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -809,7 +816,7 @@ HistoryWidget::HistoryWidget( const auto was = (_sendAs != nullptr); refreshSendAsToggle(); - if (was != (_sendAs != nullptr)) { + if (was != (_sendAs != nullptr) || _peer->starsPerMessage()) { updateControlsVisibility(); updateControlsGeometry(); orderWidgets(); @@ -832,7 +839,8 @@ HistoryWidget::HistoryWidget( return; } } - if (flags & PeerUpdateFlag::BotStartToken) { + if ((flags & PeerUpdateFlag::BotStartToken) + || (flags & PeerUpdateFlag::StarsPerMessage)) { updateControlsVisibility(); updateControlsGeometry(); } @@ -1957,6 +1965,7 @@ void HistoryWidget::setInnerFocus() { || isRecording() || isJoinChannel() || isBotStart() + || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { if (_scroll->isHidden()) { @@ -2379,6 +2388,9 @@ void HistoryWidget::showHistory( setHistory(nullptr); _list = nullptr; + if (_peer) { + _peer->cancelStarsForMessage(); + } _peer = nullptr; _topicsRequested.clear(); _canSendMessages = false; @@ -3017,16 +3029,14 @@ bool HistoryWidget::contentOverlapped(const QRect &globalRect) { } bool HistoryWidget::canWriteMessage() const { - if (!_history || !_canSendMessages) { - return false; - } - if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) { - return false; - } - if (isSearching()) { - return false; - } - return true; + return _history + && _canSendMessages + && !isBlocked() + && !isJoinChannel() + && !isMuteUnmute() + && !isBotStart() + && !isPayForMessage() + && !isSearching(); } void HistoryWidget::updateControlsVisibility() { @@ -3086,6 +3096,7 @@ void HistoryWidget::updateControlsVisibility() { || isJoinChannel() || isMuteUnmute() || isBotStart() + || isPayForMessage() || isReportMessages()))) { const auto toggle = [&](Ui::FlatButton *shown) { const auto toggleOne = [&](not_null button) { @@ -3097,6 +3108,7 @@ void HistoryWidget::updateControlsVisibility() { } }; toggleOne(_reportMessages); + toggleOne(_payForMessage); toggleOne(_joinChannel); toggleOne(_muteUnmute); toggleOne(_botStart); @@ -3110,6 +3122,8 @@ void HistoryWidget::updateControlsVisibility() { toggle(_reportMessages); } else if (isBlocked()) { toggle(_unblock); + } else if (isPayForMessage()) { + toggle(_payForMessage); } else if (isJoinChannel()) { toggle(_joinChannel); } else if (isMuteUnmute()) { @@ -3172,6 +3186,7 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); _send->show(); updateSendButtonType(); @@ -3285,6 +3300,7 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); @@ -4525,6 +4541,73 @@ void HistoryWidget::reportSelectedMessages() { } } +void HistoryWidget::payForMessage() { + if (!_peer || !session().credits().loaded()) { + return; + } else if (!_peer->starsPerMessage() || _peer->starsForMessageLocked()) { + updateControlsVisibility(); + } else if (session().local().isPeerTrustedPayForMessage(_peer->id)) { + payForMessageSure(); + } else { + const auto peer = _peer; + const auto count = peer->starsPerMessage(); + controller()->show(Box([=](not_null box) { + const auto trust = std::make_shared>(); + const auto confirmed = [=](Fn close) { + payForMessageSure((*trust)->checked()); + close(); + }; + Ui::ConfirmBox(box, { + .text = tr::lng_payment_confirm_text( + tr::now, + lt_count, + count, + lt_name, + Ui::Text::Bold(peer->shortName()), + Ui::Text::RichLangValue).append(' ').append( + tr::lng_payment_confirm_sure( + tr::now, + lt_count, + count, + Ui::Text::RichLangValue)), + .confirmed = confirmed, + .confirmText = tr::lng_payment_confirm_button(), + .title = tr::lng_payment_confirm_title(), + }); + const auto skip = st::defaultCheckbox.margin.top(); + *trust = box->addRow( + object_ptr( + box, + tr::lng_payment_confirm_dont_ask(tr::now)), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + })); + } +} + +void HistoryWidget::payForMessageSure(bool trust) { + if (trust) { + session().local().markPeerTrustedPayForMessage(_peer->id); + } + const auto required = _peer->starsPerMessage(); + if (!required) { + return; + } + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Success + || result == Settings::SmallBalanceResult::Already) { + _peer->lockStarsForMessage(); + if (canWriteMessage()) { + setInnerFocus(); + } + } + }; + Settings::MaybeRequestBalanceIncrease( + controller()->uiShow(), + required, + Settings::SmallBalanceForMessage{ .recipientId = _peer->id }, + crl::guard(this, done)); +} + History *HistoryWidget::history() const { return _history; } @@ -5071,6 +5154,40 @@ bool HistoryWidget::isSearching() const { return _composeSearch != nullptr; } +bool HistoryWidget::isPayForMessage() const { + const auto stars = _peer ? _peer->starsPerMessage() : 0; + const auto locked = _peer ? _peer->starsForMessageLocked() : 0; + if (!stars || locked) { + return false; + } else if (const auto channel = _peer->asChannel()) { + if (channel->amCreator() || channel->adminRights()) { + return false; + } + } + + const auto creating = !_payForMessageStars.current(); + _payForMessageStars = stars; + if (creating) { + session().credits().load(); + + auto text = _payForMessageStars.value( + ) | rpl::map([session = &session()](int stars) { + return tr::lng_payment_for_message( + tr::now, + lt_cost, + Ui::CreditsEmojiSmall(session).append( + Lang::FormatCountDecimal(stars)), + Ui::Text::WithEntities); + }); + Ui::SetButtonMarkedLabel( + _payForMessage, + std::move(text), + &session(), + st::historyComposeButtonText); + } + return true; +} + bool HistoryWidget::showRecordButton() const { return (_recordAvailability != Webrtc::RecordAvailability::None) && !_voiceRecordBar->isListenState() @@ -5128,6 +5245,7 @@ bool HistoryWidget::updateCmdStartShown() { && _peer->asChannel()->mgInfo->botStatus > 0))) { if (!isBotStart() && !isBlocked() + && !isPayForMessage() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) { @@ -5541,7 +5659,7 @@ void HistoryWidget::moveFieldControls() { // (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send -// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages) +// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages|_payForMessage) auto buttonsBottom = bottom - _attachToggle->height(); auto left = st::historySendRight; @@ -5615,6 +5733,7 @@ void HistoryWidget::moveFieldControls() { _joinChannel->setGeometry(fullWidthButtonRect); _muteUnmute->setGeometry(fullWidthButtonRect); _reportMessages->setGeometry(fullWidthButtonRect); + _payForMessage->setGeometry(fullWidthButtonRect); if (_sendRestriction) { _sendRestriction->setGeometry(fullWidthButtonRect); } @@ -6055,18 +6174,25 @@ void HistoryWidget::handleHistoryChange(not_null history) { const auto joinChannel = isJoinChannel(); const auto muteUnmute = isMuteUnmute(); const auto reportMessages = isReportMessages(); + const auto payForMessage = isPayForMessage(); const auto update = false || (_reportMessages->isHidden() == reportMessages) || (!reportMessages && _unblock->isHidden() == unblock) || (!reportMessages && !unblock + && _payForMessage->isHidden() == payForMessage) + || (!reportMessages + && !unblock + && !payForMessage && _botStart->isHidden() == botStart) || (!reportMessages && !unblock + && !payForMessage && !botStart && _joinChannel->isHidden() == joinChannel) || (!reportMessages && !unblock + && !payForMessage && !botStart && !joinChannel && _muteUnmute->isHidden() == muteUnmute); @@ -6430,6 +6556,7 @@ void HistoryWidget::updateHistoryGeometry( } else if (!editingMessage() && (isSearching() || isBlocked() + || isPayForMessage() || isBotStart() || isJoinChannel() || isMuteUnmute() @@ -6739,6 +6866,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (!isSearching() && !isBotStart() && !isBlocked() + && !isPayForMessage() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) @@ -8615,6 +8743,7 @@ void HistoryWidget::updateTopBarSelection() { || (_list && _list->wasSelectedText()) || isRecording() || isBotStart() + || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { _list->setFocus(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 46624566e..a6f61228e 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -422,6 +422,8 @@ private: [[nodiscard]] int computeMaxFieldHeight() const; void toggleMuteUnmute(); void reportSelectedMessages(); + void payForMessage(); + void payForMessageSure(bool trust = false); void showKeyboardHideButton(); void toggleKeyboard(bool manual = true); void startBotCommand(); @@ -640,6 +642,7 @@ private: [[nodiscard]] bool isJoinChannel() const; [[nodiscard]] bool isMuteUnmute() const; [[nodiscard]] bool isReportMessages() const; + [[nodiscard]] bool isPayForMessage() const; bool updateCmdStartShown(); void updateSendButtonType(); [[nodiscard]] bool showRecordButton() const; @@ -778,6 +781,8 @@ private: QPointer _giftToChannelIn; QPointer _giftToChannelOut; object_ptr _reportMessages; + object_ptr _payForMessage; + mutable rpl::variable _payForMessageStars; struct { object_ptr button = { nullptr }; QString text; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 94f9dfba3..8af0e66dd 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -348,6 +348,7 @@ private: std::unique_ptr _composeControls; std::unique_ptr _composeSearch; std::unique_ptr _joinGroup; + std::unique_ptr _payForMessage; std::unique_ptr _topicReopenBar; std::unique_ptr _emptyPainter; bool _skipScrollEvent = false; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 6f1004548..6cee40d89 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1015,12 +1015,12 @@ void WebViewInstance::resolveApp( void WebViewInstance::confirmOpen(Fn done) { if (_bot->isVerified() - || _session->local().isBotTrustedOpenWebView(_bot->id)) { + || _session->local().isPeerTrustedOpenWebView(_bot->id)) { done(); return; } const auto callback = [=](Fn close) { - _session->local().markBotTrustedOpenWebView(_bot->id); + _session->local().markPeerTrustedOpenWebView(_bot->id); close(); done(); }; @@ -1052,14 +1052,14 @@ void WebViewInstance::confirmAppOpen( bool forceConfirmation) { if (!forceConfirmation && (_bot->isVerified() - || _session->local().isBotTrustedOpenWebView(_bot->id))) { + || _session->local().isPeerTrustedOpenWebView(_bot->id))) { done(writeAccess); return; } _parentShow->show(Box([=](not_null box) { const auto allowed = std::make_shared(); const auto callback = [=](Fn close) { - _session->local().markBotTrustedOpenWebView(_bot->id); + _session->local().markPeerTrustedOpenWebView(_bot->id); done((*allowed) && (*allowed)->checked()); close(); }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index e9b4cd36a..630cfacaf 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -131,7 +131,8 @@ namespace Media::Stories { if (options.invertCaption) { sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } const auto done = [=] { @@ -159,7 +160,7 @@ namespace Media::Stories { MTP_inputPeerEmpty(), Data::ShortcutIdToMTP(session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=]( const MTPUpdates &result, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 7f4e26a9f..790b7a960 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -904,7 +904,7 @@ void Form::submit() { if (index < list.size() && password.isEmpty()) { _updates.fire(TmpPasswordRequired{}); return; - } else if (!_session->local().isBotTrustedPayment(_details.botId)) { + } else if (!_session->local().isPeerTrustedPayment(_details.botId)) { _updates.fire(BotTrustRequired{ .bot = _session->data().user(_details.botId), .provider = _session->data().user(_details.providerId), @@ -1307,7 +1307,7 @@ void Form::acceptTerms() { } void Form::trustBot() { - _session->local().markBotTrustedPayment(_details.botId); + _session->local().markPeerTrustedPayment(_details.botId); } void Form::processShippingOptions(const QVector &data) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 9e3153117..3e54ba9cf 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2090,6 +2090,8 @@ void SmallBalanceBox( return QString(); }, [&](SmallBalanceStarGift value) { return owner->peer(value.recipientId)->shortName(); + }, [&](SmallBalanceForMessage value) { + return owner->peer(value.recipientId)->shortName(); }); auto needed = show->session().credits().balanceValue( @@ -2128,6 +2130,11 @@ void SmallBalanceBox( lt_user, rpl::single(Ui::Text::Bold(name)), Ui::Text::RichLangValue) + : v::is(source) + ? tr::lng_credits_small_balance_for_message( + lt_user, + rpl::single(Ui::Text::Bold(name)), + Ui::Text::RichLangValue) : name.isEmpty() ? tr::lng_credits_small_balance_fallback( Ui::Text::RichLangValue) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 599ced1f3..59084cb36 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -200,12 +200,16 @@ struct SmallBalanceDeepLink { struct SmallBalanceStarGift { PeerId recipientId; }; +struct SmallBalanceForMessage { + PeerId recipientId; +}; struct SmallBalanceSource : std::variant< SmallBalanceBot, SmallBalanceReaction, SmallBalanceSubscription, SmallBalanceDeepLink, - SmallBalanceStarGift> { + SmallBalanceStarGift, + SmallBalanceForMessage> { using variant::variant; }; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 7233e0f45..1472ebf5d 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -86,7 +86,7 @@ enum { // Local Storage Keys lskSavedGifsOld = 0x0e, // no data lskSavedGifs = 0x0f, // no data lskStickersKeys = 0x10, // no data - lskTrustedBots = 0x11, // no data + lskTrustedPeers = 0x11, // no data lskFavedStickers = 0x12, // no data lskExportSettings = 0x13, // no data lskBackgroundOld = 0x14, // no data @@ -220,7 +220,7 @@ base::flat_set Account::collectGoodNames() const { _legacyBackgroundKeyDay, _recentHashtagsAndBotsKey, _exportSettingsKey, - _trustedBotsKey, + _trustedPeersKey, _installedMasksKey, _recentMasksKey, _archivedMasksKey, @@ -308,7 +308,7 @@ Account::ReadMapResult Account::readMapWith( base::flat_map draftsMap; base::flat_map draftCursorsMap; base::flat_map draftsNotReadMap; - quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0; + quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedPeersKey = 0; quint64 recentStickersKeyOld = 0; quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0; quint64 installedMasksKey = 0, recentMasksKey = 0, archivedMasksKey = 0; @@ -371,8 +371,8 @@ Account::ReadMapResult Account::readMapWith( map.stream >> reportSpamStatusesKey; ClearKey(reportSpamStatusesKey, _basePath); } break; - case lskTrustedBots: { - map.stream >> trustedBotsKey; + case lskTrustedPeers: { + map.stream >> trustedPeersKey; } break; case lskRecentStickersOld: { map.stream >> recentStickersKeyOld; @@ -459,7 +459,7 @@ Account::ReadMapResult Account::readMapWith( _draftsNotReadMap = draftsNotReadMap; _locationsKey = locationsKey; - _trustedBotsKey = trustedBotsKey; + _trustedPeersKey = trustedPeersKey; _recentStickersKeyOld = recentStickersKeyOld; _installedStickersKey = installedStickersKey; _featuredStickersKey = featuredStickersKey; @@ -573,7 +573,7 @@ void Account::writeMap() { if (!_draftsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; if (!_draftCursorsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_trustedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapSize += sizeof(quint32) + 4 * sizeof(quint64); @@ -619,8 +619,8 @@ void Account::writeMap() { if (_locationsKey) { mapData.stream << quint32(lskLocations) << quint64(_locationsKey); } - if (_trustedBotsKey) { - mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey); + if (_trustedPeersKey) { + mapData.stream << quint32(lskTrustedPeers) << quint64(_trustedPeersKey); } if (_recentStickersKeyOld) { mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); @@ -693,7 +693,7 @@ void Account::reset() { _draftsMap.clear(); _draftCursorsMap.clear(); _draftsNotReadMap.clear(); - _locationsKey = _trustedBotsKey = 0; + _locationsKey = _trustedPeersKey = 0; _recentStickersKeyOld = 0; _installedStickersKey = 0; _featuredStickersKey = 0; @@ -3147,47 +3147,47 @@ void Account::readSelf( } } -void Account::writeTrustedBots() { - if (_trustedBots.empty()) { - if (_trustedBotsKey) { - ClearKey(_trustedBotsKey, _basePath); - _trustedBotsKey = 0; +void Account::writeTrustedPeers() { + if (_trustedPeers.empty()) { + if (_trustedPeersKey) { + ClearKey(_trustedPeersKey, _basePath); + _trustedPeersKey = 0; writeMapDelayed(); } return; } - if (!_trustedBotsKey) { - _trustedBotsKey = GenerateKey(_basePath); + if (!_trustedPeersKey) { + _trustedPeersKey = GenerateKey(_basePath); writeMapQueued(); } - quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64); + quint32 size = sizeof(qint32) + _trustedPeers.size() * sizeof(quint64); EncryptedDescriptor data(size); - data.stream << qint32(_trustedBots.size()); - for (const auto &[peerId, mask] : _trustedBots) { - // value: 8 bit mask, 56 bit bot peer_id. + data.stream << qint32(_trustedPeers.size()); + for (const auto &[peerId, mask] : _trustedPeers) { + // value: 8 bit mask, 56 bit peer_id. auto value = SerializePeerId(peerId); Assert((value >> 56) == 0); value |= (quint64(mask) << 56); data.stream << value; } - FileWriteDescriptor file(_trustedBotsKey, _basePath); + FileWriteDescriptor file(_trustedPeersKey, _basePath); file.writeEncrypted(data, _localKey); } -void Account::readTrustedBots() { - if (_trustedBotsRead) { +void Account::readTrustedPeers() { + if (_trustedPeersRead) { return; } - _trustedBotsRead = true; - if (!_trustedBotsKey) { + _trustedPeersRead = true; + if (!_trustedPeersKey) { return; } FileReadDescriptor trusted; - if (!ReadEncryptedFile(trusted, _trustedBotsKey, _basePath, _localKey)) { - ClearKey(_trustedBotsKey, _basePath); - _trustedBotsKey = 0; + if (!ReadEncryptedFile(trusted, _trustedPeersKey, _basePath, _localKey)) { + ClearKey(_trustedPeersKey, _basePath); + _trustedPeersKey = 0; writeMapDelayed(); return; } @@ -3197,76 +3197,106 @@ void Account::readTrustedBots() { for (int i = 0; i < size; ++i) { auto value = quint64(); trusted.stream >> value; - const auto mask = base::flags::from_raw( + const auto mask = base::flags::from_raw( uchar(value >> 56)); const auto peerIdSerialized = value & ~(0xFFULL << 56); const auto peerId = DeserializePeerId(peerIdSerialized); - _trustedBots.emplace(peerId, mask); + _trustedPeers.emplace(peerId, mask); } } -void Account::markBotTrustedOpenGame(PeerId botId) { - if (isBotTrustedOpenGame(botId)) { +void Account::markPeerTrustedOpenGame(PeerId peerId) { + if (isPeerTrustedOpenGame(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace(botId, BotTrustFlag()); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace(peerId, PeerTrustFlag()); } else { - i->second &= ~BotTrustFlag::NoOpenGame; + i->second &= ~PeerTrustFlag::NoOpenGame; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedOpenGame(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::NoOpenGame) == 0); +bool Account::isPeerTrustedOpenGame(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::NoOpenGame) == 0); } -void Account::markBotTrustedPayment(PeerId botId) { - if (isBotTrustedPayment(botId)) { +void Account::markPeerTrustedPayment(PeerId peerId) { + if (isPeerTrustedPayment(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace( - botId, - BotTrustFlag::NoOpenGame | BotTrustFlag::Payment); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::Payment); } else { - i->second |= BotTrustFlag::Payment; + i->second |= PeerTrustFlag::Payment; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedPayment(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::Payment) != 0); +bool Account::isPeerTrustedPayment(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::Payment) != 0); } -void Account::markBotTrustedOpenWebView(PeerId botId) { - if (isBotTrustedOpenWebView(botId)) { +void Account::markPeerTrustedOpenWebView(PeerId peerId) { + if (isPeerTrustedOpenWebView(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace( - botId, - BotTrustFlag::NoOpenGame | BotTrustFlag::OpenWebView); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::OpenWebView); } else { - i->second |= BotTrustFlag::OpenWebView; + i->second |= PeerTrustFlag::OpenWebView; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedOpenWebView(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::OpenWebView) != 0); +bool Account::isPeerTrustedOpenWebView(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::OpenWebView) != 0); +} + +void Account::markPeerTrustedPayForMessage(PeerId peerId) { + if (isPeerTrustedPayForMessage(peerId)) { + return; + } + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::PayForMessage); + } else { + i->second |= PeerTrustFlag::PayForMessage; + } + writeTrustedPeers(); +} + +bool Account::isPeerTrustedPayForMessage(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::PayForMessage) != 0); +} + +void Account::clearPeerTrusted(PeerId peerId) { + const auto i = _trustedPeers.find(peerId); + if (i != end(_trustedPeers)) { + _trustedPeers.erase(i); + writeTrustedPeers(); + } } void Account::enforceModernStorageIdBots() { diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 497ec55ec..c9e977813 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -167,12 +167,15 @@ public: const QByteArray& serialized, int32 streamVersion); - void markBotTrustedOpenGame(PeerId botId); - [[nodiscard]] bool isBotTrustedOpenGame(PeerId botId); - void markBotTrustedPayment(PeerId botId); - [[nodiscard]] bool isBotTrustedPayment(PeerId botId); - void markBotTrustedOpenWebView(PeerId botId); - [[nodiscard]] bool isBotTrustedOpenWebView(PeerId botId); + void markPeerTrustedOpenGame(PeerId peerId); + [[nodiscard]] bool isPeerTrustedOpenGame(PeerId peerId); + void markPeerTrustedPayment(PeerId peerId); + [[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 enforceModernStorageIdBots(); [[nodiscard]] Webview::StorageId resolveStorageIdBots(); @@ -203,12 +206,13 @@ private: IncorrectPasscode, Failed, }; - enum class BotTrustFlag : uchar { + enum class PeerTrustFlag : uchar { NoOpenGame = (1 << 0), Payment = (1 << 1), OpenWebView = (1 << 2), + PayForMessage = (1 << 3), }; - friend inline constexpr bool is_flag_type(BotTrustFlag) { return true; }; + friend inline constexpr bool is_flag_type(PeerTrustFlag) { return true; }; [[nodiscard]] base::flat_set collectGoodNames() const; [[nodiscard]] auto prepareReadSettingsContext() const @@ -261,8 +265,8 @@ private: Data::StickersSetFlags readingFlags = 0); void importOldRecentStickers(); - void readTrustedBots(); - void writeTrustedBots(); + void readTrustedPeers(); + void writeTrustedPeers(); void readMediaLastPlaybackPositions(); void writeMediaLastPlaybackPositions(); @@ -295,7 +299,7 @@ private: Fn()> _downloadsSerialize; FileKey _locationsKey = 0; - FileKey _trustedBotsKey = 0; + FileKey _trustedPeersKey = 0; FileKey _installedStickersKey = 0; FileKey _featuredStickersKey = 0; FileKey _recentStickersKey = 0; @@ -324,8 +328,8 @@ private: qint32 _cacheTotalTimeLimit = 0; qint32 _cacheBigFileTotalTimeLimit = 0; - base::flat_map> _trustedBots; - bool _trustedBotsRead = false; + base::flat_map> _trustedPeers; + bool _trustedPeersRead = false; bool _readingUserSettings = false; bool _recentHashtagsAndBotsWereRead = false; bool _searchSuggestionsRead = false;