/* 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_statistics.h" #include "apiwrap.h" #include "data/data_channel.h" #include "data/data_peer.h" #include "main/main_session.h" #include "statistics/statistics_data_deserialize.h" namespace Api { namespace { [[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( const MTPStatsGraph &tl) { return tl.match([&](const MTPDstatsGraph &d) { using namespace Statistic; const auto zoomToken = d.vzoom_token().has_value() ? qs(*d.vzoom_token()).toUtf8() : QByteArray(); return Data::StatisticalGraph{ StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()), zoomToken, }; }, [&](const MTPDstatsGraphAsync &data) { return Data::StatisticalGraph{ .zoomToken = qs(data.vtoken()).toUtf8(), }; }, [&](const MTPDstatsGraphError &data) { return Data::StatisticalGraph{ Data::StatisticalChart() }; }); } [[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( const MTPStatsAbsValueAndPrev &tl) { const auto current = tl.data().vcurrent().v; const auto previous = tl.data().vprevious().v; return Data::StatisticalValue{ .value = current, .previousValue = previous, .growthRatePercentage = previous ? std::abs((current - previous) / float64(previous) * 100.) : 0, }; } [[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL( const MTPDstats_broadcastStats &data) { const auto &tlUnmuted = data.venabled_notifications().data(); const auto unmuted = (!tlUnmuted.vtotal().v) ? 0. : std::clamp( tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100., 0., 100.); using Recent = MTPMessageInteractionCounters; auto recentMessages = ranges::views::all( data.vrecent_message_interactions().v ) | ranges::views::transform([&](const Recent &tl) { return Data::StatisticsMessageInteractionInfo{ .messageId = tl.data().vmsg_id().v, .viewsCount = tl.data().vviews().v, .forwardsCount = tl.data().vforwards().v, }; }) | ranges::to_vector; return { .startDate = data.vperiod().data().vmin_date().v, .endDate = data.vperiod().data().vmax_date().v, .memberCount = StatisticalValueFromTL(data.vfollowers()), .meanViewCount = StatisticalValueFromTL(data.vviews_per_post()), .meanShareCount = StatisticalValueFromTL(data.vshares_per_post()), .enabledNotificationsPercentage = unmuted, .memberCountGraph = StatisticalGraphFromTL( data.vgrowth_graph()), .joinGraph = StatisticalGraphFromTL( data.vfollowers_graph()), .muteGraph = StatisticalGraphFromTL( data.vmute_graph()), .viewCountByHourGraph = StatisticalGraphFromTL( data.vtop_hours_graph()), .viewCountBySourceGraph = StatisticalGraphFromTL( data.vviews_by_source_graph()), .joinBySourceGraph = StatisticalGraphFromTL( data.vnew_followers_by_source_graph()), .languageGraph = StatisticalGraphFromTL( data.vlanguages_graph()), .messageInteractionGraph = StatisticalGraphFromTL( data.vinteractions_graph()), .instantViewInteractionGraph = StatisticalGraphFromTL( data.viv_interactions_graph()), .recentMessageInteractions = std::move(recentMessages), }; } [[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL( const MTPDstats_megagroupStats &data) { using Senders = MTPStatsGroupTopPoster; using Administrators = MTPStatsGroupTopAdmin; using Inviters = MTPStatsGroupTopInviter; auto topSenders = ranges::views::all( data.vtop_posters().v ) | ranges::views::transform([&](const Senders &tl) { return Data::StatisticsMessageSenderInfo{ .userId = UserId(tl.data().vuser_id().v), .sentMessageCount = tl.data().vmessages().v, .averageCharacterCount = tl.data().vavg_chars().v, }; }) | ranges::to_vector; auto topAdministrators = ranges::views::all( data.vtop_admins().v ) | ranges::views::transform([&](const Administrators &tl) { return Data::StatisticsAdministratorActionsInfo{ .userId = UserId(tl.data().vuser_id().v), .deletedMessageCount = tl.data().vdeleted().v, .bannedUserCount = tl.data().vkicked().v, .restrictedUserCount = tl.data().vbanned().v, }; }) | ranges::to_vector; auto topInviters = ranges::views::all( data.vtop_inviters().v ) | ranges::views::transform([&](const Inviters &tl) { return Data::StatisticsInviterInfo{ .userId = UserId(tl.data().vuser_id().v), .addedMemberCount = tl.data().vinvitations().v, }; }) | ranges::to_vector; return { .startDate = data.vperiod().data().vmin_date().v, .endDate = data.vperiod().data().vmax_date().v, .memberCount = StatisticalValueFromTL(data.vmembers()), .messageCount = StatisticalValueFromTL(data.vmessages()), .viewerCount = StatisticalValueFromTL(data.vviewers()), .senderCount = StatisticalValueFromTL(data.vposters()), .memberCountGraph = StatisticalGraphFromTL( data.vgrowth_graph()), .joinGraph = StatisticalGraphFromTL( data.vmembers_graph()), .joinBySourceGraph = StatisticalGraphFromTL( data.vnew_members_by_source_graph()), .languageGraph = StatisticalGraphFromTL( data.vlanguages_graph()), .messageContentGraph = StatisticalGraphFromTL( data.vmessages_graph()), .actionGraph = StatisticalGraphFromTL( data.vactions_graph()), .dayGraph = StatisticalGraphFromTL( data.vtop_hours_graph()), .weekGraph = StatisticalGraphFromTL( data.vweekdays_graph()), .topSenders = std::move(topSenders), .topAdministrators = std::move(topAdministrators), .topInviters = std::move(topInviters), }; } } // namespace Statistics::Statistics(not_null api) : _api(&api->instance()) { } rpl::producer Statistics::request( not_null peer) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto channel = peer->asChannel(); if (!channel) { return lifetime; } if (!channel->isMegagroup()) { _api.request(MTPstats_GetBroadcastStats( MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), channel->inputChannel )).done([=](const MTPstats_BroadcastStats &result) { _channelStats = ChannelStatisticsFromTL(result.data()); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); } else { _api.request(MTPstats_GetMegagroupStats( MTP_flags(MTPstats_GetMegagroupStats::Flags(0)), channel->inputChannel )).done([=](const MTPstats_MegagroupStats &result) { _supergroupStats = SupergroupStatisticsFromTL(result.data()); peer->owner().processUsers(result.data().vusers()); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); } return lifetime; }; } Statistics::GraphResult Statistics::requestZoom( not_null peer, const QString &token, float64 x) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto channel = peer->asChannel(); if (!channel) { return lifetime; } _api.request(MTPstats_LoadAsyncGraph( MTP_flags(x ? MTPstats_LoadAsyncGraph::Flag::f_x : MTPstats_LoadAsyncGraph::Flag(0)), MTP_string(token), MTP_long(x) )).done([=](const MTPStatsGraph &result) { consumer.put_next(StatisticalGraphFromTL(result)); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); return lifetime; }; } Statistics::GraphResult Statistics::requestMessage( not_null peer, MsgId msgId) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); const auto channel = peer->asChannel(); if (!channel) { return lifetime; } _api.request(MTPstats_GetMessageStats( MTP_flags(MTPstats_GetMessageStats::Flags(0)), channel->inputChannel, MTP_int(msgId.bare) )).done([=](const MTPstats_MessageStats &result) { consumer.put_next( StatisticalGraphFromTL(result.data().vviews_graph())); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); return lifetime; }; } Data::ChannelStatistics Statistics::channelStats() const { return _channelStats; } Data::SupergroupStatistics Statistics::supergroupStats() const { return _supergroupStats; } } // namespace Api