api_credits.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "api/api_credits.h"
  8. #include "api/api_premium.h"
  9. #include "api/api_statistics_data_deserialize.h"
  10. #include "api/api_updates.h"
  11. #include "apiwrap.h"
  12. #include "base/unixtime.h"
  13. #include "data/components/credits.h"
  14. #include "data/data_channel.h"
  15. #include "data/data_document.h"
  16. #include "data/data_peer.h"
  17. #include "data/data_photo.h"
  18. #include "data/data_session.h"
  19. #include "data/data_user.h"
  20. #include "main/main_app_config.h"
  21. #include "main/main_session.h"
  22. namespace Api {
  23. namespace {
  24. constexpr auto kTransactionsLimit = 100;
  25. [[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
  26. const MTPStarsTransaction &tl,
  27. not_null<PeerData*> peer) {
  28. using HistoryPeerTL = MTPDstarsTransactionPeer;
  29. using namespace Data;
  30. const auto owner = &peer->owner();
  31. const auto photo = tl.data().vphoto()
  32. ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
  33. : nullptr;
  34. auto extended = std::vector<CreditsHistoryMedia>();
  35. if (const auto list = tl.data().vextended_media()) {
  36. extended.reserve(list->v.size());
  37. for (const auto &media : list->v) {
  38. media.match([&](const MTPDmessageMediaPhoto &data) {
  39. if (const auto inner = data.vphoto()) {
  40. const auto photo = owner->processPhoto(*inner);
  41. if (!photo->isNull()) {
  42. extended.push_back(CreditsHistoryMedia{
  43. .type = CreditsHistoryMediaType::Photo,
  44. .id = photo->id,
  45. });
  46. }
  47. }
  48. }, [&](const MTPDmessageMediaDocument &data) {
  49. if (const auto inner = data.vdocument()) {
  50. const auto document = owner->processDocument(
  51. *inner,
  52. data.valt_documents());
  53. if (document->isAnimation()
  54. || document->isVideoFile()
  55. || document->isGifv()) {
  56. extended.push_back(CreditsHistoryMedia{
  57. .type = CreditsHistoryMediaType::Video,
  58. .id = document->id,
  59. });
  60. }
  61. }
  62. }, [&](const auto &) {});
  63. }
  64. }
  65. const auto barePeerId = tl.data().vpeer().match([](
  66. const HistoryPeerTL &p) {
  67. return peerFromMTP(p.vpeer());
  68. }, [](const auto &) {
  69. return PeerId(0);
  70. }).value;
  71. const auto stargift = tl.data().vstargift();
  72. const auto nonUniqueGift = stargift
  73. ? stargift->match([&](const MTPDstarGift &data) {
  74. return &data;
  75. }, [](const auto &) { return (const MTPDstarGift*)nullptr; })
  76. : nullptr;
  77. const auto reaction = tl.data().is_reaction();
  78. const auto amount = Data::FromTL(tl.data().vstars());
  79. const auto starrefAmount = tl.data().vstarref_amount()
  80. ? Data::FromTL(*tl.data().vstarref_amount())
  81. : StarsAmount();
  82. const auto starrefCommission
  83. = tl.data().vstarref_commission_permille().value_or_empty();
  84. const auto starrefBarePeerId = tl.data().vstarref_peer()
  85. ? peerFromMTP(*tl.data().vstarref_peer()).value
  86. : 0;
  87. const auto incoming = (amount >= StarsAmount());
  88. const auto paidMessagesCount
  89. = tl.data().vpaid_messages().value_or_empty();
  90. const auto premiumMonthsForStars
  91. = tl.data().vpremium_gift_months().value_or_empty();
  92. const auto saveActorId = (reaction
  93. || !extended.empty()
  94. || paidMessagesCount) && incoming;
  95. const auto parsedGift = stargift
  96. ? FromTL(&peer->session(), *stargift)
  97. : std::optional<Data::StarGift>();
  98. const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
  99. return Data::CreditsHistoryEntry{
  100. .id = qs(tl.data().vid()),
  101. .title = qs(tl.data().vtitle().value_or_empty()),
  102. .description = { qs(tl.data().vdescription().value_or_empty()) },
  103. .date = base::unixtime::parse(tl.data().vdate().v),
  104. .photoId = photo ? photo->id : 0,
  105. .extended = std::move(extended),
  106. .credits = Data::FromTL(tl.data().vstars()),
  107. .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
  108. .barePeerId = saveActorId ? peer->id.value : barePeerId,
  109. .bareGiveawayMsgId = uint64(
  110. tl.data().vgiveaway_post_id().value_or_empty()),
  111. .bareGiftStickerId = giftStickerId,
  112. .bareActorId = saveActorId ? barePeerId : uint64(0),
  113. .uniqueGift = parsedGift ? parsedGift->unique : nullptr,
  114. .starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
  115. .starrefCommission = paidMessagesCount ? 0 : starrefCommission,
  116. .starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
  117. .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
  118. return Data::CreditsHistoryEntry::PeerType::Peer;
  119. }, [](const MTPDstarsTransactionPeerPlayMarket &) {
  120. return Data::CreditsHistoryEntry::PeerType::PlayMarket;
  121. }, [](const MTPDstarsTransactionPeerFragment &) {
  122. return Data::CreditsHistoryEntry::PeerType::Fragment;
  123. }, [](const MTPDstarsTransactionPeerAppStore &) {
  124. return Data::CreditsHistoryEntry::PeerType::AppStore;
  125. }, [](const MTPDstarsTransactionPeerUnsupported &) {
  126. return Data::CreditsHistoryEntry::PeerType::Unsupported;
  127. }, [](const MTPDstarsTransactionPeerPremiumBot &) {
  128. return Data::CreditsHistoryEntry::PeerType::PremiumBot;
  129. }, [](const MTPDstarsTransactionPeerAds &) {
  130. return Data::CreditsHistoryEntry::PeerType::Ads;
  131. }, [](const MTPDstarsTransactionPeerAPI &) {
  132. return Data::CreditsHistoryEntry::PeerType::API;
  133. }),
  134. .subscriptionUntil = tl.data().vsubscription_period()
  135. ? base::unixtime::parse(base::unixtime::now()
  136. + tl.data().vsubscription_period()->v)
  137. : QDateTime(),
  138. .successDate = tl.data().vtransaction_date()
  139. ? base::unixtime::parse(tl.data().vtransaction_date()->v)
  140. : QDateTime(),
  141. .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
  142. .paidMessagesCount = paidMessagesCount,
  143. .paidMessagesAmount = (paidMessagesCount
  144. ? starrefAmount
  145. : StarsAmount()),
  146. .paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
  147. .starsConverted = int(nonUniqueGift
  148. ? nonUniqueGift->vconvert_stars().v
  149. : 0),
  150. .premiumMonthsForStars = premiumMonthsForStars,
  151. .floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
  152. .converted = stargift && incoming,
  153. .stargift = stargift.has_value(),
  154. .giftUpgraded = tl.data().is_stargift_upgrade(),
  155. .reaction = tl.data().is_reaction(),
  156. .refunded = tl.data().is_refund(),
  157. .pending = tl.data().is_pending(),
  158. .failed = tl.data().is_failed(),
  159. .in = incoming,
  160. .gift = tl.data().is_gift() || stargift.has_value(),
  161. };
  162. }
  163. [[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
  164. const MTPStarsSubscription &tl,
  165. not_null<PeerData*> peer) {
  166. return Data::SubscriptionEntry{
  167. .id = qs(tl.data().vid()),
  168. .inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
  169. .title = qs(tl.data().vtitle().value_or_empty()),
  170. .slug = qs(tl.data().vinvoice_slug().value_or_empty()),
  171. .until = base::unixtime::parse(tl.data().vuntil_date().v),
  172. .subscription = Data::PeerSubscription{
  173. .credits = tl.data().vpricing().data().vamount().v,
  174. .period = tl.data().vpricing().data().vperiod().v,
  175. },
  176. .barePeerId = peerFromMTP(tl.data().vpeer()).value,
  177. .photoId = (tl.data().vphoto()
  178. ? peer->owner().photoFromWeb(
  179. *tl.data().vphoto(),
  180. ImageLocation())->id
  181. : 0),
  182. .cancelled = tl.data().is_canceled(),
  183. .cancelledByBot = tl.data().is_bot_canceled(),
  184. .expired = (base::unixtime::now() > tl.data().vuntil_date().v),
  185. .canRefulfill = tl.data().is_can_refulfill(),
  186. };
  187. }
  188. [[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
  189. const MTPpayments_StarsStatus &status,
  190. not_null<PeerData*> peer) {
  191. const auto &data = status.data();
  192. peer->owner().processUsers(data.vusers());
  193. peer->owner().processChats(data.vchats());
  194. auto entries = std::vector<Data::CreditsHistoryEntry>();
  195. if (const auto history = data.vhistory()) {
  196. entries.reserve(history->v.size());
  197. for (const auto &tl : history->v) {
  198. entries.push_back(HistoryFromTL(tl, peer));
  199. }
  200. }
  201. auto subscriptions = std::vector<Data::SubscriptionEntry>();
  202. if (const auto history = data.vsubscriptions()) {
  203. subscriptions.reserve(history->v.size());
  204. for (const auto &tl : history->v) {
  205. subscriptions.push_back(SubscriptionFromTL(tl, peer));
  206. }
  207. }
  208. return Data::CreditsStatusSlice{
  209. .list = std::move(entries),
  210. .subscriptions = std::move(subscriptions),
  211. .balance = Data::FromTL(status.data().vbalance()),
  212. .subscriptionsMissingBalance
  213. = status.data().vsubscriptions_missing_balance().value_or_empty(),
  214. .allLoaded = !status.data().vnext_offset().has_value()
  215. && !status.data().vsubscriptions_next_offset().has_value(),
  216. .token = qs(status.data().vnext_offset().value_or_empty()),
  217. .tokenSubscriptions = qs(
  218. status.data().vsubscriptions_next_offset().value_or_empty()),
  219. };
  220. }
  221. } // namespace
  222. CreditsTopupOptions::CreditsTopupOptions(not_null<PeerData*> peer)
  223. : _peer(peer)
  224. , _api(&peer->session().api().instance()) {
  225. }
  226. rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
  227. return [=](auto consumer) {
  228. auto lifetime = rpl::lifetime();
  229. const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0;
  230. const auto optionsFromTL = [giftBarePeerId](const auto &options) {
  231. return ranges::views::all(
  232. options
  233. ) | ranges::views::transform([=](const auto &option) {
  234. return Data::CreditTopupOption{
  235. .credits = option.data().vstars().v,
  236. .product = qs(
  237. option.data().vstore_product().value_or_empty()),
  238. .currency = qs(option.data().vcurrency()),
  239. .amount = option.data().vamount().v,
  240. .extended = option.data().is_extended(),
  241. .giftBarePeerId = giftBarePeerId,
  242. };
  243. }) | ranges::to_vector;
  244. };
  245. const auto fail = [=](const MTP::Error &error) {
  246. consumer.put_error_copy(error.type());
  247. };
  248. if (_peer->isSelf()) {
  249. using TLOption = MTPStarsTopupOption;
  250. _api.request(MTPpayments_GetStarsTopupOptions(
  251. )).done([=](const MTPVector<TLOption> &result) {
  252. _options = optionsFromTL(result.v);
  253. consumer.put_done();
  254. }).fail(fail).send();
  255. } else if (const auto user = _peer->asUser()) {
  256. using TLOption = MTPStarsGiftOption;
  257. _api.request(MTPpayments_GetStarsGiftOptions(
  258. MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id),
  259. user->inputUser
  260. )).done([=](const MTPVector<TLOption> &result) {
  261. _options = optionsFromTL(result.v);
  262. consumer.put_done();
  263. }).fail(fail).send();
  264. }
  265. return lifetime;
  266. };
  267. }
  268. Data::CreditTopupOptions CreditsTopupOptions::options() const {
  269. return _options;
  270. }
  271. CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
  272. : _peer(peer)
  273. , _api(&peer->session().api().instance()) {
  274. }
  275. void CreditsStatus::request(
  276. const Data::CreditsStatusSlice::OffsetToken &token,
  277. Fn<void(Data::CreditsStatusSlice)> done) {
  278. if (_requestId) {
  279. return;
  280. }
  281. using TLResult = MTPpayments_StarsStatus;
  282. _requestId = _api.request(MTPpayments_GetStarsStatus(
  283. _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
  284. )).done([=](const TLResult &result) {
  285. _requestId = 0;
  286. const auto &balance = result.data().vbalance();
  287. _peer->session().credits().apply(_peer->id, Data::FromTL(balance));
  288. if (const auto onstack = done) {
  289. onstack(StatusFromTL(result, _peer));
  290. }
  291. }).fail([=] {
  292. _requestId = 0;
  293. if (const auto onstack = done) {
  294. onstack({});
  295. }
  296. }).send();
  297. }
  298. CreditsHistory::CreditsHistory(not_null<PeerData*> peer, bool in, bool out)
  299. : _peer(peer)
  300. , _flags((in == out)
  301. ? HistoryTL::Flags(0)
  302. : HistoryTL::Flags(0)
  303. | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
  304. | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
  305. , _api(&peer->session().api().instance()) {
  306. }
  307. void CreditsHistory::request(
  308. const Data::CreditsStatusSlice::OffsetToken &token,
  309. Fn<void(Data::CreditsStatusSlice)> done) {
  310. if (_requestId) {
  311. return;
  312. }
  313. _requestId = _api.request(MTPpayments_GetStarsTransactions(
  314. MTP_flags(_flags),
  315. MTPstring(), // subscription_id
  316. _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
  317. MTP_string(token),
  318. MTP_int(kTransactionsLimit)
  319. )).done([=](const MTPpayments_StarsStatus &result) {
  320. _requestId = 0;
  321. done(StatusFromTL(result, _peer));
  322. }).fail([=] {
  323. _requestId = 0;
  324. done({});
  325. }).send();
  326. }
  327. void CreditsHistory::requestSubscriptions(
  328. const Data::CreditsStatusSlice::OffsetToken &token,
  329. Fn<void(Data::CreditsStatusSlice)> done) {
  330. if (_requestId) {
  331. return;
  332. }
  333. _requestId = _api.request(MTPpayments_GetStarsSubscriptions(
  334. MTP_flags(0),
  335. _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
  336. MTP_string(token)
  337. )).done([=](const MTPpayments_StarsStatus &result) {
  338. _requestId = 0;
  339. done(StatusFromTL(result, _peer));
  340. }).fail([=] {
  341. _requestId = 0;
  342. done({});
  343. }).send();
  344. }
  345. rpl::producer<not_null<PeerData*>> PremiumPeerBot(
  346. not_null<Main::Session*> session) {
  347. const auto username = session->appConfig().get<QString>(
  348. u"premium_bot_username"_q,
  349. QString());
  350. if (username.isEmpty()) {
  351. return rpl::never<not_null<PeerData*>>();
  352. }
  353. if (const auto p = session->data().peerByUsername(username)) {
  354. return rpl::single<not_null<PeerData*>>(p);
  355. }
  356. return [=](auto consumer) {
  357. auto lifetime = rpl::lifetime();
  358. const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
  359. api->request(MTPcontacts_ResolveUsername(
  360. MTP_flags(0),
  361. MTP_string(username),
  362. MTP_string()
  363. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  364. session->data().processUsers(result.data().vusers());
  365. session->data().processChats(result.data().vchats());
  366. const auto botPeer = session->data().peerLoaded(
  367. peerFromMTP(result.data().vpeer()));
  368. if (!botPeer) {
  369. return consumer.put_done();
  370. }
  371. consumer.put_next(not_null{ botPeer });
  372. }).send();
  373. return lifetime;
  374. };
  375. }
  376. CreditsEarnStatistics::CreditsEarnStatistics(not_null<PeerData*> peer)
  377. : StatisticsRequestSender(peer)
  378. , _isUser(peer->isUser()) {
  379. }
  380. rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
  381. return [=](auto consumer) {
  382. auto lifetime = rpl::lifetime();
  383. const auto finish = [=](const QString &url) {
  384. makeRequest(MTPpayments_GetStarsRevenueStats(
  385. MTP_flags(0),
  386. (_isUser ? user()->input : channel()->input)
  387. )).done([=](const MTPpayments_StarsRevenueStats &result) {
  388. const auto &data = result.data();
  389. const auto &status = data.vstatus().data();
  390. using Data::FromTL;
  391. _data = Data::CreditsEarnStatistics{
  392. .revenueGraph = StatisticalGraphFromTL(
  393. data.vrevenue_graph()),
  394. .currentBalance = FromTL(status.vcurrent_balance()),
  395. .availableBalance = FromTL(status.vavailable_balance()),
  396. .overallRevenue = FromTL(status.voverall_revenue()),
  397. .usdRate = data.vusd_rate().v,
  398. .isWithdrawalEnabled = status.is_withdrawal_enabled(),
  399. .nextWithdrawalAt = status.vnext_withdrawal_at()
  400. ? base::unixtime::parse(
  401. status.vnext_withdrawal_at()->v)
  402. : QDateTime(),
  403. .buyAdsUrl = url,
  404. };
  405. consumer.put_done();
  406. }).fail([=](const MTP::Error &error) {
  407. consumer.put_error_copy(error.type());
  408. }).send();
  409. };
  410. makeRequest(
  411. MTPpayments_GetStarsRevenueAdsAccountUrl(
  412. (_isUser ? user()->input : channel()->input))
  413. ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
  414. finish(qs(result.data().vurl()));
  415. }).fail([=](const MTP::Error &error) {
  416. finish({});
  417. }).send();
  418. return lifetime;
  419. };
  420. }
  421. Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
  422. return _data;
  423. }
  424. CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
  425. : _peer(peer)
  426. , _api(&peer->session().api().instance()) {
  427. }
  428. rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
  429. return [=](auto consumer) {
  430. auto lifetime = rpl::lifetime();
  431. using TLOption = MTPStarsGiveawayOption;
  432. const auto optionsFromTL = [=](const auto &options) {
  433. return ranges::views::all(
  434. options
  435. ) | ranges::views::transform([=](const auto &option) {
  436. return Data::CreditsGiveawayOption{
  437. .winners = ranges::views::all(
  438. option.data().vwinners().v
  439. ) | ranges::views::transform([](const auto &winner) {
  440. return Data::CreditsGiveawayOption::Winner{
  441. .users = winner.data().vusers().v,
  442. .perUserStars = winner.data().vper_user_stars().v,
  443. .isDefault = winner.data().is_default(),
  444. };
  445. }) | ranges::to_vector,
  446. .storeProduct = qs(
  447. option.data().vstore_product().value_or_empty()),
  448. .currency = qs(option.data().vcurrency()),
  449. .amount = option.data().vamount().v,
  450. .credits = option.data().vstars().v,
  451. .yearlyBoosts = option.data().vyearly_boosts().v,
  452. .isExtended = option.data().is_extended(),
  453. .isDefault = option.data().is_default(),
  454. };
  455. }) | ranges::to_vector;
  456. };
  457. const auto fail = [=](const MTP::Error &error) {
  458. consumer.put_error_copy(error.type());
  459. };
  460. _api.request(MTPpayments_GetStarsGiveawayOptions(
  461. )).done([=](const MTPVector<TLOption> &result) {
  462. _options = optionsFromTL(result.v);
  463. consumer.put_done();
  464. }).fail(fail).send();
  465. return lifetime;
  466. };
  467. }
  468. Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
  469. return _options;
  470. }
  471. void EditCreditsSubscription(
  472. not_null<Main::Session*> session,
  473. const QString &id,
  474. bool cancel,
  475. Fn<void()> done,
  476. Fn<void(QString)> fail) {
  477. using Flag = MTPpayments_ChangeStarsSubscription::Flag;
  478. session->api().request(
  479. MTPpayments_ChangeStarsSubscription(
  480. MTP_flags(Flag::f_canceled),
  481. MTP_inputPeerSelf(),
  482. MTP_string(id),
  483. MTP_bool(cancel)
  484. )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
  485. }
  486. MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
  487. return id.isUser()
  488. ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
  489. : MTP_inputSavedStarGiftChat(
  490. id.chat()->input,
  491. MTP_long(id.chatSavedId()));
  492. }
  493. } // namespace Api