api_statistics.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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_statistics.h"
  8. #include "api/api_statistics_data_deserialize.h"
  9. #include "apiwrap.h"
  10. #include "base/unixtime.h"
  11. #include "data/data_channel.h"
  12. #include "data/data_session.h"
  13. #include "data/data_stories.h"
  14. #include "data/data_story.h"
  15. #include "data/data_user.h"
  16. #include "history/history.h"
  17. #include "main/main_session.h"
  18. namespace Api {
  19. namespace {
  20. [[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
  21. const MTPStatsAbsValueAndPrev &tl) {
  22. const auto current = tl.data().vcurrent().v;
  23. const auto previous = tl.data().vprevious().v;
  24. return Data::StatisticalValue{
  25. .value = current,
  26. .previousValue = previous,
  27. .growthRatePercentage = previous
  28. ? std::abs((current - previous) / float64(previous) * 100.)
  29. : 0,
  30. };
  31. }
  32. [[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL(
  33. const MTPDstats_broadcastStats &data) {
  34. const auto &tlUnmuted = data.venabled_notifications().data();
  35. const auto unmuted = (!tlUnmuted.vtotal().v)
  36. ? 0.
  37. : std::clamp(
  38. tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100.,
  39. 0.,
  40. 100.);
  41. using Recent = MTPPostInteractionCounters;
  42. auto recentMessages = ranges::views::all(
  43. data.vrecent_posts_interactions().v
  44. ) | ranges::views::transform([&](const Recent &tl) {
  45. return tl.match([&](const MTPDpostInteractionCountersStory &data) {
  46. return Data::StatisticsMessageInteractionInfo{
  47. .storyId = data.vstory_id().v,
  48. .viewsCount = data.vviews().v,
  49. .forwardsCount = data.vforwards().v,
  50. .reactionsCount = data.vreactions().v,
  51. };
  52. }, [&](const MTPDpostInteractionCountersMessage &data) {
  53. return Data::StatisticsMessageInteractionInfo{
  54. .messageId = data.vmsg_id().v,
  55. .viewsCount = data.vviews().v,
  56. .forwardsCount = data.vforwards().v,
  57. .reactionsCount = data.vreactions().v,
  58. };
  59. });
  60. }) | ranges::to_vector;
  61. return {
  62. .startDate = data.vperiod().data().vmin_date().v,
  63. .endDate = data.vperiod().data().vmax_date().v,
  64. .memberCount = StatisticalValueFromTL(data.vfollowers()),
  65. .meanViewCount = StatisticalValueFromTL(data.vviews_per_post()),
  66. .meanShareCount = StatisticalValueFromTL(data.vshares_per_post()),
  67. .meanReactionCount = StatisticalValueFromTL(
  68. data.vreactions_per_post()),
  69. .meanStoryViewCount = StatisticalValueFromTL(
  70. data.vviews_per_story()),
  71. .meanStoryShareCount = StatisticalValueFromTL(
  72. data.vshares_per_story()),
  73. .meanStoryReactionCount = StatisticalValueFromTL(
  74. data.vreactions_per_story()),
  75. .enabledNotificationsPercentage = unmuted,
  76. .memberCountGraph = StatisticalGraphFromTL(
  77. data.vgrowth_graph()),
  78. .joinGraph = StatisticalGraphFromTL(
  79. data.vfollowers_graph()),
  80. .muteGraph = StatisticalGraphFromTL(
  81. data.vmute_graph()),
  82. .viewCountByHourGraph = StatisticalGraphFromTL(
  83. data.vtop_hours_graph()),
  84. .viewCountBySourceGraph = StatisticalGraphFromTL(
  85. data.vviews_by_source_graph()),
  86. .joinBySourceGraph = StatisticalGraphFromTL(
  87. data.vnew_followers_by_source_graph()),
  88. .languageGraph = StatisticalGraphFromTL(
  89. data.vlanguages_graph()),
  90. .messageInteractionGraph = StatisticalGraphFromTL(
  91. data.vinteractions_graph()),
  92. .instantViewInteractionGraph = StatisticalGraphFromTL(
  93. data.viv_interactions_graph()),
  94. .reactionsByEmotionGraph = StatisticalGraphFromTL(
  95. data.vreactions_by_emotion_graph()),
  96. .storyInteractionsGraph = StatisticalGraphFromTL(
  97. data.vstory_interactions_graph()),
  98. .storyReactionsByEmotionGraph = StatisticalGraphFromTL(
  99. data.vstory_reactions_by_emotion_graph()),
  100. .recentMessageInteractions = std::move(recentMessages),
  101. };
  102. }
  103. [[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL(
  104. const MTPDstats_megagroupStats &data) {
  105. using Senders = MTPStatsGroupTopPoster;
  106. using Administrators = MTPStatsGroupTopAdmin;
  107. using Inviters = MTPStatsGroupTopInviter;
  108. auto topSenders = ranges::views::all(
  109. data.vtop_posters().v
  110. ) | ranges::views::transform([&](const Senders &tl) {
  111. return Data::StatisticsMessageSenderInfo{
  112. .userId = UserId(tl.data().vuser_id().v),
  113. .sentMessageCount = tl.data().vmessages().v,
  114. .averageCharacterCount = tl.data().vavg_chars().v,
  115. };
  116. }) | ranges::to_vector;
  117. auto topAdministrators = ranges::views::all(
  118. data.vtop_admins().v
  119. ) | ranges::views::transform([&](const Administrators &tl) {
  120. return Data::StatisticsAdministratorActionsInfo{
  121. .userId = UserId(tl.data().vuser_id().v),
  122. .deletedMessageCount = tl.data().vdeleted().v,
  123. .bannedUserCount = tl.data().vkicked().v,
  124. .restrictedUserCount = tl.data().vbanned().v,
  125. };
  126. }) | ranges::to_vector;
  127. auto topInviters = ranges::views::all(
  128. data.vtop_inviters().v
  129. ) | ranges::views::transform([&](const Inviters &tl) {
  130. return Data::StatisticsInviterInfo{
  131. .userId = UserId(tl.data().vuser_id().v),
  132. .addedMemberCount = tl.data().vinvitations().v,
  133. };
  134. }) | ranges::to_vector;
  135. return {
  136. .startDate = data.vperiod().data().vmin_date().v,
  137. .endDate = data.vperiod().data().vmax_date().v,
  138. .memberCount = StatisticalValueFromTL(data.vmembers()),
  139. .messageCount = StatisticalValueFromTL(data.vmessages()),
  140. .viewerCount = StatisticalValueFromTL(data.vviewers()),
  141. .senderCount = StatisticalValueFromTL(data.vposters()),
  142. .memberCountGraph = StatisticalGraphFromTL(
  143. data.vgrowth_graph()),
  144. .joinGraph = StatisticalGraphFromTL(
  145. data.vmembers_graph()),
  146. .joinBySourceGraph = StatisticalGraphFromTL(
  147. data.vnew_members_by_source_graph()),
  148. .languageGraph = StatisticalGraphFromTL(
  149. data.vlanguages_graph()),
  150. .messageContentGraph = StatisticalGraphFromTL(
  151. data.vmessages_graph()),
  152. .actionGraph = StatisticalGraphFromTL(
  153. data.vactions_graph()),
  154. .dayGraph = StatisticalGraphFromTL(
  155. data.vtop_hours_graph()),
  156. .weekGraph = StatisticalGraphFromTL(
  157. data.vweekdays_graph()),
  158. .topSenders = std::move(topSenders),
  159. .topAdministrators = std::move(topAdministrators),
  160. .topInviters = std::move(topInviters),
  161. };
  162. }
  163. } // namespace
  164. Statistics::Statistics(not_null<ChannelData*> channel)
  165. : StatisticsRequestSender(channel) {
  166. }
  167. rpl::producer<rpl::no_value, QString> Statistics::request() {
  168. return [=](auto consumer) {
  169. auto lifetime = rpl::lifetime();
  170. if (!channel()->isMegagroup()) {
  171. makeRequest(MTPstats_GetBroadcastStats(
  172. MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
  173. channel()->inputChannel
  174. )).done([=](const MTPstats_BroadcastStats &result) {
  175. _channelStats = ChannelStatisticsFromTL(result.data());
  176. consumer.put_done();
  177. }).fail([=](const MTP::Error &error) {
  178. consumer.put_error_copy(error.type());
  179. }).send();
  180. } else {
  181. makeRequest(MTPstats_GetMegagroupStats(
  182. MTP_flags(MTPstats_GetMegagroupStats::Flags(0)),
  183. channel()->inputChannel
  184. )).done([=](const MTPstats_MegagroupStats &result) {
  185. const auto &data = result.data();
  186. _supergroupStats = SupergroupStatisticsFromTL(data);
  187. channel()->owner().processUsers(data.vusers());
  188. consumer.put_done();
  189. }).fail([=](const MTP::Error &error) {
  190. consumer.put_error_copy(error.type());
  191. }).send();
  192. }
  193. return lifetime;
  194. };
  195. }
  196. Statistics::GraphResult Statistics::requestZoom(
  197. const QString &token,
  198. float64 x) {
  199. return [=](auto consumer) {
  200. auto lifetime = rpl::lifetime();
  201. const auto wasEmpty = _zoomDeque.empty();
  202. _zoomDeque.push_back([=] {
  203. makeRequest(MTPstats_LoadAsyncGraph(
  204. MTP_flags(x
  205. ? MTPstats_LoadAsyncGraph::Flag::f_x
  206. : MTPstats_LoadAsyncGraph::Flag(0)),
  207. MTP_string(token),
  208. MTP_long(x)
  209. )).done([=](const MTPStatsGraph &result) {
  210. consumer.put_next(StatisticalGraphFromTL(result));
  211. consumer.put_done();
  212. if (!_zoomDeque.empty()) {
  213. _zoomDeque.pop_front();
  214. if (!_zoomDeque.empty()) {
  215. _zoomDeque.front()();
  216. }
  217. }
  218. }).fail([=](const MTP::Error &error) {
  219. consumer.put_error_copy(error.type());
  220. }).send();
  221. });
  222. if (wasEmpty) {
  223. _zoomDeque.front()();
  224. }
  225. return lifetime;
  226. };
  227. }
  228. Data::ChannelStatistics Statistics::channelStats() const {
  229. return _channelStats;
  230. }
  231. Data::SupergroupStatistics Statistics::supergroupStats() const {
  232. return _supergroupStats;
  233. }
  234. PublicForwards::PublicForwards(
  235. not_null<ChannelData*> channel,
  236. Data::RecentPostId fullId)
  237. : StatisticsRequestSender(channel)
  238. , _fullId(fullId) {
  239. }
  240. void PublicForwards::request(
  241. const Data::PublicForwardsSlice::OffsetToken &token,
  242. Fn<void(Data::PublicForwardsSlice)> done) {
  243. if (_requestId) {
  244. return;
  245. }
  246. const auto channel = StatisticsRequestSender::channel();
  247. const auto processResult = [=](const MTPstats_PublicForwards &tl) {
  248. using Messages = QVector<Data::RecentPostId>;
  249. _requestId = 0;
  250. const auto &data = tl.data();
  251. auto &owner = channel->owner();
  252. owner.processUsers(data.vusers());
  253. owner.processChats(data.vchats());
  254. const auto nextToken = data.vnext_offset()
  255. ? qs(*data.vnext_offset())
  256. : Data::PublicForwardsSlice::OffsetToken();
  257. const auto fullCount = data.vcount().v;
  258. auto recentList = Messages(data.vforwards().v.size());
  259. for (const auto &tlForward : data.vforwards().v) {
  260. tlForward.match([&](const MTPDpublicForwardMessage &data) {
  261. const auto &message = data.vmessage();
  262. const auto msgId = IdFromMessage(message);
  263. const auto peerId = PeerFromMessage(message);
  264. const auto lastDate = DateFromMessage(message);
  265. if (const auto peer = owner.peerLoaded(peerId)) {
  266. if (!lastDate) {
  267. return;
  268. }
  269. owner.addNewMessage(
  270. message,
  271. MessageFlags(),
  272. NewMessageType::Existing);
  273. recentList.push_back({ .messageId = { peerId, msgId } });
  274. }
  275. }, [&](const MTPDpublicForwardStory &data) {
  276. const auto story = owner.stories().applySingle(
  277. peerFromMTP(data.vpeer()),
  278. data.vstory());
  279. if (story) {
  280. recentList.push_back({ .storyId = story->fullId() });
  281. }
  282. });
  283. }
  284. const auto allLoaded = nextToken.isEmpty() || (nextToken == token);
  285. _lastTotal = std::max(_lastTotal, fullCount);
  286. done({
  287. .list = std::move(recentList),
  288. .total = _lastTotal,
  289. .allLoaded = allLoaded,
  290. .token = nextToken,
  291. });
  292. };
  293. const auto processFail = [=] {
  294. _requestId = 0;
  295. done({});
  296. };
  297. constexpr auto kLimit = tl::make_int(100);
  298. if (_fullId.messageId) {
  299. _requestId = makeRequest(MTPstats_GetMessagePublicForwards(
  300. channel->inputChannel,
  301. MTP_int(_fullId.messageId.msg),
  302. MTP_string(token),
  303. kLimit
  304. )).done(processResult).fail(processFail).send();
  305. } else if (_fullId.storyId) {
  306. _requestId = makeRequest(MTPstats_GetStoryPublicForwards(
  307. channel->input,
  308. MTP_int(_fullId.storyId.story),
  309. MTP_string(token),
  310. kLimit
  311. )).done(processResult).fail(processFail).send();
  312. }
  313. }
  314. MessageStatistics::MessageStatistics(
  315. not_null<ChannelData*> channel,
  316. FullMsgId fullId)
  317. : StatisticsRequestSender(channel)
  318. , _publicForwards(channel, { .messageId = fullId })
  319. , _fullId(fullId) {
  320. }
  321. MessageStatistics::MessageStatistics(
  322. not_null<ChannelData*> channel,
  323. FullStoryId storyId)
  324. : StatisticsRequestSender(channel)
  325. , _publicForwards(channel, { .storyId = storyId })
  326. , _storyId(storyId) {
  327. }
  328. Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
  329. return _firstSlice;
  330. }
  331. void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
  332. if (channel()->isMegagroup() && !_storyId) {
  333. return;
  334. }
  335. const auto requestFirstPublicForwards = [=](
  336. const Data::StatisticalGraph &messageGraph,
  337. const Data::StatisticalGraph &reactionsGraph,
  338. const Data::StatisticsMessageInteractionInfo &info) {
  339. const auto callback = [=](Data::PublicForwardsSlice slice) {
  340. const auto total = slice.total;
  341. _firstSlice = std::move(slice);
  342. done({
  343. .messageInteractionGraph = messageGraph,
  344. .reactionsByEmotionGraph = reactionsGraph,
  345. .publicForwards = total,
  346. .privateForwards = info.forwardsCount - total,
  347. .views = info.viewsCount,
  348. .reactions = info.reactionsCount,
  349. });
  350. };
  351. _publicForwards.request({}, callback);
  352. };
  353. const auto requestPrivateForwards = [=](
  354. const Data::StatisticalGraph &messageGraph,
  355. const Data::StatisticalGraph &reactionsGraph) {
  356. api().request(MTPchannels_GetMessages(
  357. channel()->inputChannel,
  358. MTP_vector<MTPInputMessage>(
  359. 1,
  360. MTP_inputMessageID(MTP_int(_fullId.msg))))
  361. ).done([=](const MTPmessages_Messages &result) {
  362. const auto process = [&](const MTPVector<MTPMessage> &messages) {
  363. const auto &message = messages.v.front();
  364. return message.match([&](const MTPDmessage &data) {
  365. auto reactionsCount = 0;
  366. if (const auto tlReactions = data.vreactions()) {
  367. const auto &tlCounts = tlReactions->data().vresults();
  368. for (const auto &tlCount : tlCounts.v) {
  369. reactionsCount += tlCount.data().vcount().v;
  370. }
  371. }
  372. return Data::StatisticsMessageInteractionInfo{
  373. .messageId = IdFromMessage(message),
  374. .viewsCount = data.vviews()
  375. ? data.vviews()->v
  376. : 0,
  377. .forwardsCount = data.vforwards()
  378. ? data.vforwards()->v
  379. : 0,
  380. .reactionsCount = reactionsCount,
  381. };
  382. }, [](const MTPDmessageEmpty &) {
  383. return Data::StatisticsMessageInteractionInfo();
  384. }, [](const MTPDmessageService &) {
  385. return Data::StatisticsMessageInteractionInfo();
  386. });
  387. };
  388. auto info = result.match([&](const MTPDmessages_messages &data) {
  389. return process(data.vmessages());
  390. }, [&](const MTPDmessages_messagesSlice &data) {
  391. return process(data.vmessages());
  392. }, [&](const MTPDmessages_channelMessages &data) {
  393. return process(data.vmessages());
  394. }, [](const MTPDmessages_messagesNotModified &) {
  395. return Data::StatisticsMessageInteractionInfo();
  396. });
  397. requestFirstPublicForwards(
  398. messageGraph,
  399. reactionsGraph,
  400. std::move(info));
  401. }).fail([=](const MTP::Error &error) {
  402. requestFirstPublicForwards(messageGraph, reactionsGraph, {});
  403. }).send();
  404. };
  405. const auto requestStoryPrivateForwards = [=](
  406. const Data::StatisticalGraph &messageGraph,
  407. const Data::StatisticalGraph &reactionsGraph) {
  408. api().request(MTPstories_GetStoriesByID(
  409. channel()->input,
  410. MTP_vector<MTPint>(1, MTP_int(_storyId.story)))
  411. ).done([=](const MTPstories_Stories &result) {
  412. const auto &storyItem = result.data().vstories().v.front();
  413. auto info = storyItem.match([&](const MTPDstoryItem &data) {
  414. if (!data.vviews()) {
  415. return Data::StatisticsMessageInteractionInfo();
  416. }
  417. const auto &tlViews = data.vviews()->data();
  418. return Data::StatisticsMessageInteractionInfo{
  419. .storyId = data.vid().v,
  420. .viewsCount = tlViews.vviews_count().v,
  421. .forwardsCount = tlViews.vforwards_count().value_or(0),
  422. .reactionsCount = tlViews.vreactions_count().value_or(0),
  423. };
  424. }, [](const auto &) {
  425. return Data::StatisticsMessageInteractionInfo();
  426. });
  427. requestFirstPublicForwards(
  428. messageGraph,
  429. reactionsGraph,
  430. std::move(info));
  431. }).fail([=](const MTP::Error &error) {
  432. requestFirstPublicForwards(messageGraph, reactionsGraph, {});
  433. }).send();
  434. };
  435. if (_storyId) {
  436. makeRequest(MTPstats_GetStoryStats(
  437. MTP_flags(MTPstats_GetStoryStats::Flags(0)),
  438. channel()->input,
  439. MTP_int(_storyId.story)
  440. )).done([=](const MTPstats_StoryStats &result) {
  441. const auto &data = result.data();
  442. requestStoryPrivateForwards(
  443. StatisticalGraphFromTL(data.vviews_graph()),
  444. StatisticalGraphFromTL(data.vreactions_by_emotion_graph()));
  445. }).fail([=](const MTP::Error &error) {
  446. requestStoryPrivateForwards({}, {});
  447. }).send();
  448. } else {
  449. makeRequest(MTPstats_GetMessageStats(
  450. MTP_flags(MTPstats_GetMessageStats::Flags(0)),
  451. channel()->inputChannel,
  452. MTP_int(_fullId.msg.bare)
  453. )).done([=](const MTPstats_MessageStats &result) {
  454. const auto &data = result.data();
  455. requestPrivateForwards(
  456. StatisticalGraphFromTL(data.vviews_graph()),
  457. StatisticalGraphFromTL(data.vreactions_by_emotion_graph()));
  458. }).fail([=](const MTP::Error &error) {
  459. requestPrivateForwards({}, {});
  460. }).send();
  461. }
  462. }
  463. Boosts::Boosts(not_null<PeerData*> peer)
  464. : _peer(peer)
  465. , _api(&peer->session().api().instance()) {
  466. }
  467. rpl::producer<rpl::no_value, QString> Boosts::request() {
  468. return [=](auto consumer) {
  469. auto lifetime = rpl::lifetime();
  470. const auto channel = _peer->asChannel();
  471. if (!channel) {
  472. return lifetime;
  473. }
  474. _api.request(MTPpremium_GetBoostsStatus(
  475. _peer->input
  476. )).done([=](const MTPpremium_BoostsStatus &result) {
  477. const auto &data = result.data();
  478. channel->updateLevelHint(data.vlevel().v);
  479. const auto hasPremium = !!data.vpremium_audience();
  480. const auto premiumMemberCount = hasPremium
  481. ? std::max(0, int(data.vpremium_audience()->data().vpart().v))
  482. : 0;
  483. const auto participantCount = hasPremium
  484. ? std::max(
  485. int(data.vpremium_audience()->data().vtotal().v),
  486. premiumMemberCount)
  487. : 0;
  488. const auto premiumMemberPercentage = (participantCount > 0)
  489. ? (100. * premiumMemberCount / participantCount)
  490. : 0;
  491. const auto slots = data.vmy_boost_slots();
  492. _boostStatus.overview = Data::BoostsOverview{
  493. .group = channel->isMegagroup(),
  494. .mine = slots ? int(slots->v.size()) : 0,
  495. .level = std::max(data.vlevel().v, 0),
  496. .boostCount = std::max(
  497. data.vboosts().v,
  498. data.vcurrent_level_boosts().v),
  499. .currentLevelBoostCount = data.vcurrent_level_boosts().v,
  500. .nextLevelBoostCount = data.vnext_level_boosts()
  501. ? data.vnext_level_boosts()->v
  502. : 0,
  503. .premiumMemberCount = premiumMemberCount,
  504. .premiumMemberPercentage = premiumMemberPercentage,
  505. };
  506. _boostStatus.link = qs(data.vboost_url());
  507. if (data.vprepaid_giveaways()) {
  508. _boostStatus.prepaidGiveaway = ranges::views::all(
  509. data.vprepaid_giveaways()->v
  510. ) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
  511. return r.match([&](const MTPDprepaidGiveaway &data) {
  512. return Data::BoostPrepaidGiveaway{
  513. .date = base::unixtime::parse(data.vdate().v),
  514. .id = data.vid().v,
  515. .months = data.vmonths().v,
  516. .quantity = data.vquantity().v,
  517. };
  518. }, [&](const MTPDprepaidStarsGiveaway &data) {
  519. return Data::BoostPrepaidGiveaway{
  520. .date = base::unixtime::parse(data.vdate().v),
  521. .id = data.vid().v,
  522. .credits = data.vstars().v,
  523. .quantity = data.vquantity().v,
  524. .boosts = data.vboosts().v,
  525. };
  526. });
  527. }) | ranges::to_vector;
  528. }
  529. using namespace Data;
  530. requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) {
  531. _boostStatus.firstSliceBoosts = std::move(slice);
  532. requestBoosts({ .gifts = true }, [=](BoostsListSlice &&s) {
  533. _boostStatus.firstSliceGifts = std::move(s);
  534. consumer.put_done();
  535. });
  536. });
  537. }).fail([=](const MTP::Error &error) {
  538. consumer.put_error_copy(error.type());
  539. }).send();
  540. return lifetime;
  541. };
  542. }
  543. void Boosts::requestBoosts(
  544. const Data::BoostsListSlice::OffsetToken &token,
  545. Fn<void(Data::BoostsListSlice)> done) {
  546. if (_requestId) {
  547. return;
  548. }
  549. constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
  550. constexpr auto kTlLimit = tl::make_int(kLimit);
  551. const auto gifts = token.gifts;
  552. _requestId = _api.request(MTPpremium_GetBoostsList(
  553. gifts
  554. ? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts)
  555. : MTP_flags(0),
  556. _peer->input,
  557. MTP_string(token.next),
  558. token.next.isEmpty() ? kTlFirstSlice : kTlLimit
  559. )).done([=](const MTPpremium_BoostsList &result) {
  560. _requestId = 0;
  561. const auto &data = result.data();
  562. _peer->owner().processUsers(data.vusers());
  563. auto list = std::vector<Data::Boost>();
  564. list.reserve(data.vboosts().v.size());
  565. constexpr auto kMonthsDivider = int(30 * 86400);
  566. for (const auto &boost : data.vboosts().v) {
  567. const auto &data = boost.data();
  568. const auto path = data.vused_gift_slug()
  569. ? (u"giftcode/"_q + qs(data.vused_gift_slug()->v))
  570. : QString();
  571. auto giftCodeLink = !path.isEmpty()
  572. ? Data::GiftCodeLink{
  573. _peer->session().createInternalLink(path),
  574. _peer->session().createInternalLinkFull(path),
  575. qs(data.vused_gift_slug()->v),
  576. }
  577. : Data::GiftCodeLink();
  578. list.push_back({
  579. .id = qs(data.vid()),
  580. .userId = UserId(data.vuser_id().value_or_empty()),
  581. .giveawayMessage = data.vgiveaway_msg_id()
  582. ? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
  583. : FullMsgId(),
  584. .date = base::unixtime::parse(data.vdate().v),
  585. .expiresAt = base::unixtime::parse(data.vexpires().v),
  586. .expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
  587. / kMonthsDivider),
  588. .giftCodeLink = std::move(giftCodeLink),
  589. .multiplier = data.vmultiplier().value_or_empty(),
  590. .credits = data.vstars().value_or_empty(),
  591. .isGift = data.is_gift(),
  592. .isGiveaway = data.is_giveaway(),
  593. .isUnclaimed = data.is_unclaimed(),
  594. });
  595. }
  596. done(Data::BoostsListSlice{
  597. .list = std::move(list),
  598. .multipliedTotal = data.vcount().v,
  599. .allLoaded = (data.vcount().v == data.vboosts().v.size()),
  600. .token = Data::BoostsListSlice::OffsetToken{
  601. .next = data.vnext_offset()
  602. ? qs(*data.vnext_offset())
  603. : QString(),
  604. .gifts = gifts,
  605. },
  606. });
  607. }).fail([=] {
  608. _requestId = 0;
  609. }).send();
  610. }
  611. Data::BoostStatus Boosts::boostStatus() const {
  612. return _boostStatus;
  613. }
  614. EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
  615. : StatisticsRequestSender(peer)
  616. , _isUser(peer->isUser()) {
  617. }
  618. rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
  619. return [=](auto consumer) {
  620. auto lifetime = rpl::lifetime();
  621. makeRequest(MTPstats_GetBroadcastRevenueStats(
  622. MTP_flags(0),
  623. (_isUser ? user()->input : channel()->input)
  624. )).done([=](const MTPstats_BroadcastRevenueStats &result) {
  625. const auto &data = result.data();
  626. const auto &balances = data.vbalances().data();
  627. _data = Data::EarnStatistics{
  628. .topHoursGraph = StatisticalGraphFromTL(
  629. data.vtop_hours_graph()),
  630. .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()),
  631. .currentBalance = balances.vcurrent_balance().v,
  632. .availableBalance = balances.vavailable_balance().v,
  633. .overallRevenue = balances.voverall_revenue().v,
  634. .usdRate = data.vusd_rate().v,
  635. };
  636. requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
  637. _data.firstHistorySlice = std::move(slice);
  638. if (!_isUser) {
  639. api().request(
  640. MTPchannels_GetFullChannel(channel()->inputChannel)
  641. ).done([=](const MTPmessages_ChatFull &result) {
  642. result.data().vfull_chat().match([&](
  643. const MTPDchannelFull &d) {
  644. _data.switchedOff = d.is_restricted_sponsored();
  645. }, [](const auto &) {
  646. });
  647. consumer.put_done();
  648. }).fail([=](const MTP::Error &error) {
  649. consumer.put_error_copy(error.type());
  650. }).send();
  651. } else {
  652. consumer.put_done();
  653. }
  654. });
  655. }).fail([=](const MTP::Error &error) {
  656. consumer.put_error_copy(error.type());
  657. }).send();
  658. return lifetime;
  659. };
  660. }
  661. void EarnStatistics::requestHistory(
  662. const Data::EarnHistorySlice::OffsetToken &token,
  663. Fn<void(Data::EarnHistorySlice)> done) {
  664. if (_requestId) {
  665. return;
  666. }
  667. constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
  668. constexpr auto kTlLimit = tl::make_int(kLimit);
  669. _requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
  670. (_isUser ? user()->input : channel()->input),
  671. MTP_int(token),
  672. (!token) ? kTlFirstSlice : kTlLimit
  673. )).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
  674. _requestId = 0;
  675. const auto &tlTransactions = result.data().vtransactions().v;
  676. auto list = std::vector<Data::EarnHistoryEntry>();
  677. list.reserve(tlTransactions.size());
  678. for (const auto &tlTransaction : tlTransactions) {
  679. list.push_back(tlTransaction.match([&](
  680. const MTPDbroadcastRevenueTransactionProceeds &d) {
  681. return Data::EarnHistoryEntry{
  682. .type = Data::EarnHistoryEntry::Type::In,
  683. .amount = d.vamount().v,
  684. .date = base::unixtime::parse(d.vfrom_date().v),
  685. .dateTo = base::unixtime::parse(d.vto_date().v),
  686. };
  687. }, [&](const MTPDbroadcastRevenueTransactionWithdrawal &d) {
  688. return Data::EarnHistoryEntry{
  689. .type = Data::EarnHistoryEntry::Type::Out,
  690. .status = d.is_pending()
  691. ? Data::EarnHistoryEntry::Status::Pending
  692. : d.is_failed()
  693. ? Data::EarnHistoryEntry::Status::Failed
  694. : Data::EarnHistoryEntry::Status::Success,
  695. .amount = (std::numeric_limits<Data::EarnInt>::max()
  696. - d.vamount().v
  697. + 1),
  698. .date = base::unixtime::parse(d.vdate().v),
  699. // .provider = qs(d.vprovider()),
  700. .successDate = d.vtransaction_date()
  701. ? base::unixtime::parse(d.vtransaction_date()->v)
  702. : QDateTime(),
  703. .successLink = d.vtransaction_url()
  704. ? qs(*d.vtransaction_url())
  705. : QString(),
  706. };
  707. }, [&](const MTPDbroadcastRevenueTransactionRefund &d) {
  708. return Data::EarnHistoryEntry{
  709. .type = Data::EarnHistoryEntry::Type::Return,
  710. .amount = d.vamount().v,
  711. .date = base::unixtime::parse(d.vdate().v),
  712. // .provider = qs(d.vprovider()),
  713. };
  714. }));
  715. }
  716. const auto nextToken = token + tlTransactions.size();
  717. done(Data::EarnHistorySlice{
  718. .list = std::move(list),
  719. .total = result.data().vcount().v,
  720. .allLoaded = (result.data().vcount().v == nextToken),
  721. .token = Data::EarnHistorySlice::OffsetToken(nextToken),
  722. });
  723. }).fail([=] {
  724. done({});
  725. _requestId = 0;
  726. }).send();
  727. }
  728. Data::EarnStatistics EarnStatistics::data() const {
  729. return _data;
  730. }
  731. } // namespace Api