/* 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_statistics_data_deserialize.h" #include "api/api_updates.h" #include "apiwrap.h" #include "base/unixtime.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 peer) { using HistoryPeerTL = MTPDstarsTransactionPeer; const auto photo = tl.data().vphoto() ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; const auto barePeerId = tl.data().vpeer().match([]( const HistoryPeerTL &p) { return peerFromMTP(p.vpeer()); }, [](const auto &) { return PeerId(0); }).value; 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, .credits = tl.data().vstars().v, .bareId = barePeerId, .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; }), .refunded = tl.data().is_refund(), .pending = tl.data().is_pending(), .failed = tl.data().is_failed(), .finishDate = tl.data().vtransaction_date() ? base::unixtime::parse(tl.data().vtransaction_date()->v) : QDateTime(), .finishUrl = qs(tl.data().vtransaction_url().value_or_empty()), .in = (!barePeerId || tl.data().is_refund()) && !tl.data().is_pending() && !tl.data().is_failed(), }; } [[nodiscard]] Data::CreditsStatusSlice StatusFromTL( const MTPpayments_StarsStatus &status, not_null peer) { peer->owner().processUsers(status.data().vusers()); peer->owner().processChats(status.data().vchats()); return Data::CreditsStatusSlice{ .list = ranges::views::all( status.data().vhistory().v ) | ranges::views::transform([&](const MTPStarsTransaction &tl) { return HistoryFromTL(tl, peer); }) | ranges::to_vector, .balance = status.data().vbalance().v, .allLoaded = !status.data().vnext_offset().has_value(), .token = qs(status.data().vnext_offset().value_or_empty()), }; } } // namespace CreditsTopupOptions::CreditsTopupOptions(not_null peer) : _peer(peer) , _api(&peer->session().api().instance()) { } rpl::producer CreditsTopupOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); using TLOption = MTPStarsTopupOption; _api.request(MTPpayments_GetStarsTopupOptions( )).done([=](const MTPVector &result) { _options = ranges::views::all( result.v ) | ranges::views::transform([](const TLOption &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(), }; }) | ranges::to_vector; consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); return lifetime; }; } CreditsStatus::CreditsStatus(not_null peer) : _peer(peer) , _api(&peer->session().api().instance()) { } void CreditsStatus::request( const Data::CreditsStatusSlice::OffsetToken &token, Fn 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; done(StatusFromTL(result, _peer)); }).fail([=] { _requestId = 0; done({}); }).send(); } CreditsHistory::CreditsHistory(not_null 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 done) { if (_requestId) { return; } _requestId = _api.request(MTPpayments_GetStarsTransactions( MTP_flags(_flags), _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(); } Data::CreditTopupOptions CreditsTopupOptions::options() const { return _options; } rpl::producer> PremiumPeerBot( not_null session) { const auto username = session->appConfig().get( u"premium_bot_username"_q, QString()); if (username.isEmpty()) { return rpl::never>(); } if (const auto p = session->data().peerByUsername(username)) { return rpl::single>(p); } return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto api = lifetime.make_state(&session->mtp()); api->request(MTPcontacts_ResolveUsername( MTP_string(username) )).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; }; } BotEarnStatistics::BotEarnStatistics(not_null user) : StatisticsRequestSender(user) { } rpl::producer BotEarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); makeRequest(MTPpayments_GetStarsRevenueStats( MTP_flags(0), user()->input )).done([=](const MTPpayments_StarsRevenueStats &result) { const auto &data = result.data(); const auto &status = data.vstatus().data(); _data = Data::BotEarnStatistics{ .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()), .currentBalance = status.vcurrent_balance().v, .availableBalance = status.vavailable_balance().v, .overallRevenue = status.voverall_revenue().v, .usdRate = data.vusd_rate().v, .isWithdrawalEnabled = status.is_withdrawal_enabled(), .nextWithdrawalAt = status.vnext_withdrawal_at() ? base::unixtime::parse(status.vnext_withdrawal_at()->v) : QDateTime(), }; consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); return lifetime; }; } Data::BotEarnStatistics BotEarnStatistics::data() const { return _data; } } // namespace Api