Proof-of-concept read time in private chats.

This commit is contained in:
John Preston 2024-01-09 17:49:02 +04:00
parent e63d573414
commit 474f1118b6
4 changed files with 128 additions and 20 deletions

View file

@ -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<WhoReadPeer> list;
bool unknown = false;
WhoReadState state = WhoReadState::Empty;
friend inline bool operator==(
const Peers &a,
@ -59,7 +62,7 @@ struct PeersWithReactions {
std::vector<PeerWithReaction> list;
std::vector<WhoReadPeer> 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<Peers> data;
mtpRequestId requestId = 0;
@ -76,7 +79,7 @@ struct CachedRead {
struct CachedReacted {
CachedReacted()
: data(PeersWithReactions{ .unknown = true }) {
: data(PeersWithReactions{ .state = WhoReadState::Unknown }) {
}
rpl::variable<PeersWithReactions> 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<Ui::WhoReadContent> 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<HistoryItem*> 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<int>(
"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)

View file

@ -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<Window::SessionController*> controller) {
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
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 = [=] {

View file

@ -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<QAction*> Action::action() const {

View file

@ -38,13 +38,21 @@ enum class WhoReadType {
Reacted,
};
enum class WhoReadState : uchar {
Empty,
Unknown,
MyHidden,
HisHidden,
TooOld,
};
struct WhoReadContent {
std::vector<WhoReadParticipant> participants;
WhoReadType type = WhoReadType::Seen;
QString singleCustomEntityData;
int fullReactionsCount = 0;
int fullReadCount = 0;
bool unknown = false;
WhoReadState state = WhoReadState::Empty;
};
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(