| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "api/api_credits.h"
- #include "api/api_premium.h"
- #include "api/api_statistics_data_deserialize.h"
- #include "api/api_updates.h"
- #include "apiwrap.h"
- #include "base/unixtime.h"
- #include "data/components/credits.h"
- #include "data/data_channel.h"
- #include "data/data_document.h"
- #include "data/data_peer.h"
- #include "data/data_photo.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "main/main_app_config.h"
- #include "main/main_session.h"
- namespace Api {
- namespace {
- constexpr auto kTransactionsLimit = 100;
- [[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
- const MTPStarsTransaction &tl,
- not_null<PeerData*> peer) {
- using HistoryPeerTL = MTPDstarsTransactionPeer;
- using namespace Data;
- const auto owner = &peer->owner();
- const auto photo = tl.data().vphoto()
- ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
- : nullptr;
- auto extended = std::vector<CreditsHistoryMedia>();
- if (const auto list = tl.data().vextended_media()) {
- extended.reserve(list->v.size());
- for (const auto &media : list->v) {
- media.match([&](const MTPDmessageMediaPhoto &data) {
- if (const auto inner = data.vphoto()) {
- const auto photo = owner->processPhoto(*inner);
- if (!photo->isNull()) {
- extended.push_back(CreditsHistoryMedia{
- .type = CreditsHistoryMediaType::Photo,
- .id = photo->id,
- });
- }
- }
- }, [&](const MTPDmessageMediaDocument &data) {
- if (const auto inner = data.vdocument()) {
- const auto document = owner->processDocument(
- *inner,
- data.valt_documents());
- if (document->isAnimation()
- || document->isVideoFile()
- || document->isGifv()) {
- extended.push_back(CreditsHistoryMedia{
- .type = CreditsHistoryMediaType::Video,
- .id = document->id,
- });
- }
- }
- }, [&](const auto &) {});
- }
- }
- const auto barePeerId = tl.data().vpeer().match([](
- const HistoryPeerTL &p) {
- return peerFromMTP(p.vpeer());
- }, [](const auto &) {
- return PeerId(0);
- }).value;
- const auto stargift = tl.data().vstargift();
- const auto nonUniqueGift = stargift
- ? stargift->match([&](const MTPDstarGift &data) {
- return &data;
- }, [](const auto &) { return (const MTPDstarGift*)nullptr; })
- : nullptr;
- const auto reaction = tl.data().is_reaction();
- const auto amount = Data::FromTL(tl.data().vstars());
- const auto starrefAmount = tl.data().vstarref_amount()
- ? Data::FromTL(*tl.data().vstarref_amount())
- : StarsAmount();
- const auto starrefCommission
- = tl.data().vstarref_commission_permille().value_or_empty();
- const auto starrefBarePeerId = tl.data().vstarref_peer()
- ? peerFromMTP(*tl.data().vstarref_peer()).value
- : 0;
- const auto incoming = (amount >= StarsAmount());
- const auto paidMessagesCount
- = tl.data().vpaid_messages().value_or_empty();
- const auto premiumMonthsForStars
- = tl.data().vpremium_gift_months().value_or_empty();
- const auto saveActorId = (reaction
- || !extended.empty()
- || paidMessagesCount) && incoming;
- const auto parsedGift = stargift
- ? FromTL(&peer->session(), *stargift)
- : std::optional<Data::StarGift>();
- const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
- return Data::CreditsHistoryEntry{
- .id = qs(tl.data().vid()),
- .title = qs(tl.data().vtitle().value_or_empty()),
- .description = { qs(tl.data().vdescription().value_or_empty()) },
- .date = base::unixtime::parse(tl.data().vdate().v),
- .photoId = photo ? photo->id : 0,
- .extended = std::move(extended),
- .credits = Data::FromTL(tl.data().vstars()),
- .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
- .barePeerId = saveActorId ? peer->id.value : barePeerId,
- .bareGiveawayMsgId = uint64(
- tl.data().vgiveaway_post_id().value_or_empty()),
- .bareGiftStickerId = giftStickerId,
- .bareActorId = saveActorId ? barePeerId : uint64(0),
- .uniqueGift = parsedGift ? parsedGift->unique : nullptr,
- .starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
- .starrefCommission = paidMessagesCount ? 0 : starrefCommission,
- .starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
- .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
- return Data::CreditsHistoryEntry::PeerType::Peer;
- }, [](const MTPDstarsTransactionPeerPlayMarket &) {
- return Data::CreditsHistoryEntry::PeerType::PlayMarket;
- }, [](const MTPDstarsTransactionPeerFragment &) {
- return Data::CreditsHistoryEntry::PeerType::Fragment;
- }, [](const MTPDstarsTransactionPeerAppStore &) {
- return Data::CreditsHistoryEntry::PeerType::AppStore;
- }, [](const MTPDstarsTransactionPeerUnsupported &) {
- return Data::CreditsHistoryEntry::PeerType::Unsupported;
- }, [](const MTPDstarsTransactionPeerPremiumBot &) {
- return Data::CreditsHistoryEntry::PeerType::PremiumBot;
- }, [](const MTPDstarsTransactionPeerAds &) {
- return Data::CreditsHistoryEntry::PeerType::Ads;
- }, [](const MTPDstarsTransactionPeerAPI &) {
- return Data::CreditsHistoryEntry::PeerType::API;
- }),
- .subscriptionUntil = tl.data().vsubscription_period()
- ? base::unixtime::parse(base::unixtime::now()
- + tl.data().vsubscription_period()->v)
- : QDateTime(),
- .successDate = tl.data().vtransaction_date()
- ? base::unixtime::parse(tl.data().vtransaction_date()->v)
- : QDateTime(),
- .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
- .paidMessagesCount = paidMessagesCount,
- .paidMessagesAmount = (paidMessagesCount
- ? starrefAmount
- : StarsAmount()),
- .paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
- .starsConverted = int(nonUniqueGift
- ? nonUniqueGift->vconvert_stars().v
- : 0),
- .premiumMonthsForStars = premiumMonthsForStars,
- .floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
- .converted = stargift && incoming,
- .stargift = stargift.has_value(),
- .giftUpgraded = tl.data().is_stargift_upgrade(),
- .reaction = tl.data().is_reaction(),
- .refunded = tl.data().is_refund(),
- .pending = tl.data().is_pending(),
- .failed = tl.data().is_failed(),
- .in = incoming,
- .gift = tl.data().is_gift() || stargift.has_value(),
- };
- }
- [[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
- const MTPStarsSubscription &tl,
- not_null<PeerData*> peer) {
- return Data::SubscriptionEntry{
- .id = qs(tl.data().vid()),
- .inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
- .title = qs(tl.data().vtitle().value_or_empty()),
- .slug = qs(tl.data().vinvoice_slug().value_or_empty()),
- .until = base::unixtime::parse(tl.data().vuntil_date().v),
- .subscription = Data::PeerSubscription{
- .credits = tl.data().vpricing().data().vamount().v,
- .period = tl.data().vpricing().data().vperiod().v,
- },
- .barePeerId = peerFromMTP(tl.data().vpeer()).value,
- .photoId = (tl.data().vphoto()
- ? peer->owner().photoFromWeb(
- *tl.data().vphoto(),
- ImageLocation())->id
- : 0),
- .cancelled = tl.data().is_canceled(),
- .cancelledByBot = tl.data().is_bot_canceled(),
- .expired = (base::unixtime::now() > tl.data().vuntil_date().v),
- .canRefulfill = tl.data().is_can_refulfill(),
- };
- }
- [[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
- const MTPpayments_StarsStatus &status,
- not_null<PeerData*> peer) {
- const auto &data = status.data();
- peer->owner().processUsers(data.vusers());
- peer->owner().processChats(data.vchats());
- auto entries = std::vector<Data::CreditsHistoryEntry>();
- if (const auto history = data.vhistory()) {
- entries.reserve(history->v.size());
- for (const auto &tl : history->v) {
- entries.push_back(HistoryFromTL(tl, peer));
- }
- }
- auto subscriptions = std::vector<Data::SubscriptionEntry>();
- if (const auto history = data.vsubscriptions()) {
- subscriptions.reserve(history->v.size());
- for (const auto &tl : history->v) {
- subscriptions.push_back(SubscriptionFromTL(tl, peer));
- }
- }
- return Data::CreditsStatusSlice{
- .list = std::move(entries),
- .subscriptions = std::move(subscriptions),
- .balance = Data::FromTL(status.data().vbalance()),
- .subscriptionsMissingBalance
- = status.data().vsubscriptions_missing_balance().value_or_empty(),
- .allLoaded = !status.data().vnext_offset().has_value()
- && !status.data().vsubscriptions_next_offset().has_value(),
- .token = qs(status.data().vnext_offset().value_or_empty()),
- .tokenSubscriptions = qs(
- status.data().vsubscriptions_next_offset().value_or_empty()),
- };
- }
- } // namespace
- CreditsTopupOptions::CreditsTopupOptions(not_null<PeerData*> peer)
- : _peer(peer)
- , _api(&peer->session().api().instance()) {
- }
- rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0;
- const auto optionsFromTL = [giftBarePeerId](const auto &options) {
- return ranges::views::all(
- options
- ) | ranges::views::transform([=](const auto &option) {
- return Data::CreditTopupOption{
- .credits = option.data().vstars().v,
- .product = qs(
- option.data().vstore_product().value_or_empty()),
- .currency = qs(option.data().vcurrency()),
- .amount = option.data().vamount().v,
- .extended = option.data().is_extended(),
- .giftBarePeerId = giftBarePeerId,
- };
- }) | ranges::to_vector;
- };
- const auto fail = [=](const MTP::Error &error) {
- consumer.put_error_copy(error.type());
- };
- if (_peer->isSelf()) {
- using TLOption = MTPStarsTopupOption;
- _api.request(MTPpayments_GetStarsTopupOptions(
- )).done([=](const MTPVector<TLOption> &result) {
- _options = optionsFromTL(result.v);
- consumer.put_done();
- }).fail(fail).send();
- } else if (const auto user = _peer->asUser()) {
- using TLOption = MTPStarsGiftOption;
- _api.request(MTPpayments_GetStarsGiftOptions(
- MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id),
- user->inputUser
- )).done([=](const MTPVector<TLOption> &result) {
- _options = optionsFromTL(result.v);
- consumer.put_done();
- }).fail(fail).send();
- }
- return lifetime;
- };
- }
- Data::CreditTopupOptions CreditsTopupOptions::options() const {
- return _options;
- }
- CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
- : _peer(peer)
- , _api(&peer->session().api().instance()) {
- }
- void CreditsStatus::request(
- const Data::CreditsStatusSlice::OffsetToken &token,
- Fn<void(Data::CreditsStatusSlice)> done) {
- if (_requestId) {
- return;
- }
- using TLResult = MTPpayments_StarsStatus;
- _requestId = _api.request(MTPpayments_GetStarsStatus(
- _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
- )).done([=](const TLResult &result) {
- _requestId = 0;
- const auto &balance = result.data().vbalance();
- _peer->session().credits().apply(_peer->id, Data::FromTL(balance));
- if (const auto onstack = done) {
- onstack(StatusFromTL(result, _peer));
- }
- }).fail([=] {
- _requestId = 0;
- if (const auto onstack = done) {
- onstack({});
- }
- }).send();
- }
- CreditsHistory::CreditsHistory(not_null<PeerData*> peer, bool in, bool out)
- : _peer(peer)
- , _flags((in == out)
- ? HistoryTL::Flags(0)
- : HistoryTL::Flags(0)
- | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
- | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
- , _api(&peer->session().api().instance()) {
- }
- void CreditsHistory::request(
- const Data::CreditsStatusSlice::OffsetToken &token,
- Fn<void(Data::CreditsStatusSlice)> done) {
- if (_requestId) {
- return;
- }
- _requestId = _api.request(MTPpayments_GetStarsTransactions(
- MTP_flags(_flags),
- MTPstring(), // subscription_id
- _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
- MTP_string(token),
- MTP_int(kTransactionsLimit)
- )).done([=](const MTPpayments_StarsStatus &result) {
- _requestId = 0;
- done(StatusFromTL(result, _peer));
- }).fail([=] {
- _requestId = 0;
- done({});
- }).send();
- }
- void CreditsHistory::requestSubscriptions(
- const Data::CreditsStatusSlice::OffsetToken &token,
- Fn<void(Data::CreditsStatusSlice)> done) {
- if (_requestId) {
- return;
- }
- _requestId = _api.request(MTPpayments_GetStarsSubscriptions(
- MTP_flags(0),
- _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
- MTP_string(token)
- )).done([=](const MTPpayments_StarsStatus &result) {
- _requestId = 0;
- done(StatusFromTL(result, _peer));
- }).fail([=] {
- _requestId = 0;
- done({});
- }).send();
- }
- rpl::producer<not_null<PeerData*>> PremiumPeerBot(
- not_null<Main::Session*> session) {
- const auto username = session->appConfig().get<QString>(
- u"premium_bot_username"_q,
- QString());
- if (username.isEmpty()) {
- return rpl::never<not_null<PeerData*>>();
- }
- if (const auto p = session->data().peerByUsername(username)) {
- return rpl::single<not_null<PeerData*>>(p);
- }
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
- api->request(MTPcontacts_ResolveUsername(
- MTP_flags(0),
- MTP_string(username),
- MTP_string()
- )).done([=](const MTPcontacts_ResolvedPeer &result) {
- session->data().processUsers(result.data().vusers());
- session->data().processChats(result.data().vchats());
- const auto botPeer = session->data().peerLoaded(
- peerFromMTP(result.data().vpeer()));
- if (!botPeer) {
- return consumer.put_done();
- }
- consumer.put_next(not_null{ botPeer });
- }).send();
- return lifetime;
- };
- }
- CreditsEarnStatistics::CreditsEarnStatistics(not_null<PeerData*> peer)
- : StatisticsRequestSender(peer)
- , _isUser(peer->isUser()) {
- }
- rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto finish = [=](const QString &url) {
- makeRequest(MTPpayments_GetStarsRevenueStats(
- MTP_flags(0),
- (_isUser ? user()->input : channel()->input)
- )).done([=](const MTPpayments_StarsRevenueStats &result) {
- const auto &data = result.data();
- const auto &status = data.vstatus().data();
- using Data::FromTL;
- _data = Data::CreditsEarnStatistics{
- .revenueGraph = StatisticalGraphFromTL(
- data.vrevenue_graph()),
- .currentBalance = FromTL(status.vcurrent_balance()),
- .availableBalance = FromTL(status.vavailable_balance()),
- .overallRevenue = FromTL(status.voverall_revenue()),
- .usdRate = data.vusd_rate().v,
- .isWithdrawalEnabled = status.is_withdrawal_enabled(),
- .nextWithdrawalAt = status.vnext_withdrawal_at()
- ? base::unixtime::parse(
- status.vnext_withdrawal_at()->v)
- : QDateTime(),
- .buyAdsUrl = url,
- };
- consumer.put_done();
- }).fail([=](const MTP::Error &error) {
- consumer.put_error_copy(error.type());
- }).send();
- };
- makeRequest(
- MTPpayments_GetStarsRevenueAdsAccountUrl(
- (_isUser ? user()->input : channel()->input))
- ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
- finish(qs(result.data().vurl()));
- }).fail([=](const MTP::Error &error) {
- finish({});
- }).send();
- return lifetime;
- };
- }
- Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
- return _data;
- }
- CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
- : _peer(peer)
- , _api(&peer->session().api().instance()) {
- }
- rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- using TLOption = MTPStarsGiveawayOption;
- const auto optionsFromTL = [=](const auto &options) {
- return ranges::views::all(
- options
- ) | ranges::views::transform([=](const auto &option) {
- return Data::CreditsGiveawayOption{
- .winners = ranges::views::all(
- option.data().vwinners().v
- ) | ranges::views::transform([](const auto &winner) {
- return Data::CreditsGiveawayOption::Winner{
- .users = winner.data().vusers().v,
- .perUserStars = winner.data().vper_user_stars().v,
- .isDefault = winner.data().is_default(),
- };
- }) | ranges::to_vector,
- .storeProduct = qs(
- option.data().vstore_product().value_or_empty()),
- .currency = qs(option.data().vcurrency()),
- .amount = option.data().vamount().v,
- .credits = option.data().vstars().v,
- .yearlyBoosts = option.data().vyearly_boosts().v,
- .isExtended = option.data().is_extended(),
- .isDefault = option.data().is_default(),
- };
- }) | ranges::to_vector;
- };
- const auto fail = [=](const MTP::Error &error) {
- consumer.put_error_copy(error.type());
- };
- _api.request(MTPpayments_GetStarsGiveawayOptions(
- )).done([=](const MTPVector<TLOption> &result) {
- _options = optionsFromTL(result.v);
- consumer.put_done();
- }).fail(fail).send();
- return lifetime;
- };
- }
- Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
- return _options;
- }
- void EditCreditsSubscription(
- not_null<Main::Session*> session,
- const QString &id,
- bool cancel,
- Fn<void()> done,
- Fn<void(QString)> fail) {
- using Flag = MTPpayments_ChangeStarsSubscription::Flag;
- session->api().request(
- MTPpayments_ChangeStarsSubscription(
- MTP_flags(Flag::f_canceled),
- MTP_inputPeerSelf(),
- MTP_string(id),
- MTP_bool(cancel)
- )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
- }
- MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
- return id.isUser()
- ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
- : MTP_inputSavedStarGiftChat(
- id.chat()->input,
- MTP_long(id.chatSavedId()));
- }
- } // namespace Api
|