From 474f1118b606e305d84d2eb995d39e96dbb23562 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 9 Jan 2024 17:49:02 +0400 Subject: [PATCH] Proof-of-concept read time in private chats. --- Telegram/SourceFiles/api/api_who_reacted.cpp | 103 +++++++++++++++--- .../view/history_view_context_menu.cpp | 19 +++- .../controls/who_reacted_context_action.cpp | 16 ++- .../ui/controls/who_reacted_context_action.h | 10 +- 4 files changed, 128 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index f32358c06..27134dea9 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_who_reacted.h" +#include "api/api_global_privacy.h" #include "history/history_item.h" #include "history/history.h" #include "data/stickers/data_custom_emoji.h" @@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_message_reaction_id.h" +#include "data/data_peer_values.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -36,10 +38,11 @@ namespace { constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; +using WhoReadState = Ui::WhoReadState; struct Peers { std::vector list; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const Peers &a, @@ -59,7 +62,7 @@ struct PeersWithReactions { std::vector list; std::vector read; int fullReactionsCount = 0; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const PeersWithReactions &a, @@ -68,7 +71,7 @@ struct PeersWithReactions { struct CachedRead { CachedRead() - : data(Peers{ .unknown = true }) { + : data(Peers{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -76,7 +79,7 @@ struct CachedRead { struct CachedReacted { CachedReacted() - : data(PeersWithReactions{ .unknown = true }) { + : data(PeersWithReactions{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -186,6 +189,27 @@ struct State { context->cachedReacted.erase(j); } }, context->subscriptions[session]); + Data::AmPremiumValue( + session + ) | rpl::skip(1) | rpl::filter( + rpl::mappers::_1 + ) | rpl::start_with_next([=] { + for (auto &[item, cache] : context->cachedRead) { + if (cache.data.current().state == Ui::WhoReadState::MyHidden) { + cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; + } + } + }, context->subscriptions[session]); + session->api().globalPrivacy().hideReadTime( + ) | rpl::skip(1) | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + for (auto &[item, cache] : context->cachedRead) { + if (cache.data.current().state == Ui::WhoReadState::MyHidden) { + cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; + } + } + }, context->subscriptions[session]); return context; } @@ -222,7 +246,38 @@ struct State { } const auto context = PreparedContextAt(weak.data(), session); auto &entry = context->cacheRead(item); - if (!entry.requestId) { + if (entry.requestId) { + } else if (const auto user = item->history()->peer->asUser()) { + entry.requestId = session->api().request( + MTPmessages_GetOutboxReadDate( + user->input, + MTP_int(item->id) + ) + ).done([=](const MTPOutboxReadDate &result) { + const auto &data = result.data(); + auto &entry = context->cacheRead(item); + entry.requestId = 0; + auto parsed = Peers(); + parsed.list.push_back({ + .peer = user->id, + .date = data.vdate().v, + }); + entry.data = std::move(parsed); + }).fail([=](const MTP::Error &error) { + auto &entry = context->cacheRead(item); + entry.requestId = 0; + if (entry.data.current().state == WhoReadState::Unknown) { + const auto &text = error.type(); + entry.data = (text == u"YOUR_PRIVACY_RESTRICTED"_q) + ? Peers{ .state = WhoReadState::MyHidden } + : (text == u"USER_PRIVACY_RESTRICTED"_q) + ? Peers{ .state = WhoReadState::HisHidden } + : (text == u"MESSAGE_TOO_OLD"_q) + ? Peers{ .state = WhoReadState::TooOld } + : Peers{ .state = WhoReadState::Empty }; + } + }).send(); + } else { entry.requestId = session->api().request( MTPmessages_GetMessageReadParticipants( item->history()->peer->input, @@ -243,8 +298,8 @@ struct State { }).fail([=] { auto &entry = context->cacheRead(item); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = Peers(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = Peers{ .state = WhoReadState::Empty }; } }).send(); } @@ -258,7 +313,7 @@ struct State { .list = peers.list | ranges::views::transform([](WhoReadPeer peer) { return PeerWithReaction{ .peerWithDate = peer }; }) | ranges::to_vector, - .unknown = peers.unknown, + .state = peers.state, }; result.read = std::move(peers.list); return result; @@ -319,8 +374,10 @@ struct State { }).fail([=] { auto &entry = context->cacheReacted(item, reaction); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = PeersWithReactions(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = PeersWithReactions{ + .state = WhoReadState::Empty, + }; } }).send(); } @@ -336,8 +393,9 @@ struct State { WhoReactedIds(item, {}, context), WhoReadIds(item, context) ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { - if (reacted.unknown || read.unknown) { - return PeersWithReactions{ .unknown = true }; + if (reacted.state == WhoReadState::Unknown + || read.state == WhoReadState::Unknown) { + return PeersWithReactions{ .state = WhoReadState::Unknown}; } auto &list = reacted.list; for (const auto &peerWithDate : read.list) { @@ -531,16 +589,17 @@ rpl::producer WhoReacted( std::move( idsWithReactions ) | rpl::start_with_next([=](PeersWithReactions &&peers) { - if (peers.unknown) { + if (peers.state == WhoReadState::Unknown) { state->userpics.clear(); consumer.put_next(Ui::WhoReadContent{ .type = state->current.type, .fullReactionsCount = state->current.fullReactionsCount, .fullReadCount = state->current.fullReadCount, - .unknown = true, + .state = WhoReadState::Unknown, }); return; } + state->current.state = peers.state; state->current.fullReadCount = int(peers.read.size()); state->current.fullReactionsCount = peers.fullReactionsCount; if (whoReadIds) { @@ -631,6 +690,22 @@ bool WhoReadExists(not_null item) { } const auto history = item->history(); const auto peer = history->peer; + if (const auto user = peer->asUser()) { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->readDatesPrivate()) { + return false; + } + const auto &appConfig = peer->session().account().appConfig(); + const auto expirePeriod = appConfig.get( + "pm_read_date_expire_period", + 7 * 86400); + if (item->date() + int64(expirePeriod) <= int64(base::unixtime::now())) { + return false; + } + return true; + } const auto chat = peer->asChat(); const auto megagroup = peer->asMegagroup(); if ((!chat && !megagroup) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 856639aa8..7cb610211 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_attached_stickers.h" #include "api/api_editing.h" +#include "api/api_global_privacy.h" #include "api/api_polls.h" #include "api/api_report.h" #include "api/api_ringtones.h" @@ -41,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "boxes/delete_messages_box.h" #include "boxes/report_messages_box.h" +#include "boxes/premium_preview_box.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/translate_box.h" @@ -62,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "base/platform/base_platform_info.h" #include "base/call_delayed.h" +#include "settings/settings_premium.h" #include "window/window_peer_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" @@ -1265,11 +1268,23 @@ void AddWhoReactedAction( not_null controller) { const auto whoReadIds = std::make_shared(); const auto weak = Ui::MakeWeak(menu.get()); + const auto user = item->history()->peer; const auto participantChosen = [=](uint64 id) { if (const auto strong = weak.data()) { strong->hideMenu(); } - controller->showPeerInfo(PeerId(id)); + if (id) { + controller->showPeerInfo(PeerId(id)); + } else { + const auto type = ShowOrPremium::ReadTime; + auto box = Box(ShowOrPremiumBox, type, user->shortName(), [=] { + const auto api = &controller->session().api(); + api->globalPrivacy().updateHideReadTime({}); + }, [=] { + Settings::ShowPremium(controller, u"revtime_hidden"_q); + }); + controller->show(std::move(box)); + } }; const auto showAllChosen = [=, itemId = item->fullId()]{ // Pressing on an item that has a submenu doesn't hide it :( @@ -1396,7 +1411,7 @@ void ShowWhoReactedMenu( context, st::defaultWhoRead ) | rpl::filter([=](const Ui::WhoReadContent &content) { - return !content.unknown; + return content.state != Ui::WhoReadState::Unknown; }) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) { const auto creating = !*menu; const auto refillTop = [=] { diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 3d6ae7638..b6d24e3b8 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -178,7 +178,7 @@ Action::Action( ) | rpl::start_with_next([=](WhoReadContent &&content) { checkAppeared(); const auto changed = (_content.participants != content.participants) - || (_content.unknown != content.unknown); + || (_content.state != content.state); _content = content; if (changed) { PostponeCall(this, [=] { populateSubmenu(); }); @@ -219,6 +219,10 @@ Action::Action( if (const auto onstack = _showAllChosen) { onstack(); } + } else if (_content.state == WhoReadState::MyHidden) { + if (const auto onstack = _participantChosen) { + onstack(0); + } } }, lifetime()); @@ -379,8 +383,13 @@ void Action::refreshText() { const auto count = std::max(_content.fullReactionsCount, usersCount); _text.setMarkedText( _st.itemStyle, - { (_content.unknown + { ((_content.state == WhoReadState::Unknown) ? tr::lng_context_seen_loading(tr::now) + : (_content.state == WhoReadState::MyHidden) + ? tr::lng_context_read_show(tr::now) + : (_content.state == WhoReadState::HisHidden + || _content.state == WhoReadState::TooOld) + ? tr::lng_context_read_hidden(tr::now) : (usersCount == 1) ? _content.participants.front().name : (_content.fullReactionsCount > 0 @@ -431,7 +440,8 @@ void Action::refreshDimensions() { } bool Action::isEnabled() const { - return !_content.participants.empty(); + return !_content.participants.empty() + || (_content.state == WhoReadState::MyHidden); } not_null Action::action() const { diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index 0b668b524..77a54a4e2 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -38,13 +38,21 @@ enum class WhoReadType { Reacted, }; +enum class WhoReadState : uchar { + Empty, + Unknown, + MyHidden, + HisHidden, + TooOld, +}; + struct WhoReadContent { std::vector participants; WhoReadType type = WhoReadType::Seen; QString singleCustomEntityData; int fullReactionsCount = 0; int fullReadCount = 0; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; }; [[nodiscard]] base::unique_qptr WhoReactedContextAction(