Reaction notifications only from non-blocked contacts.

This commit is contained in:
John Preston 2022-01-31 16:18:40 +03:00
parent 6a9c5818ba
commit 63bf564757
6 changed files with 151 additions and 104 deletions

View file

@ -1086,8 +1086,8 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
} }
item->contributeToSlowmode(); item->contributeToSlowmode();
auto notification = ItemNotification{ auto notification = ItemNotification{
item, .item = item,
ItemNotificationType::Message, .type = ItemNotificationType::Message,
}; };
if (item->showNotification()) { if (item->showNotification()) {
pushNotification(notification); pushNotification(notification);

View file

@ -84,10 +84,13 @@ enum class ItemNotificationType {
}; };
struct ItemNotification { struct ItemNotification {
not_null<HistoryItem*> item; not_null<HistoryItem*> item;
UserData *reactionSender = nullptr;
ItemNotificationType type = ItemNotificationType::Message; ItemNotificationType type = ItemNotificationType::Message;
friend inline bool operator==(ItemNotification a, ItemNotification b) { friend inline bool operator==(ItemNotification a, ItemNotification b) {
return (a.item == b.item) && (a.type == b.type); return (a.item == b.item)
&& (a.reactionSender == b.reactionSender)
&& (a.type == b.type);
} }
}; };

View file

@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kNotificationTextLimit = 255; constexpr auto kNotificationTextLimit = 255;
constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case.
using ItemPreview = HistoryView::ItemPreview; using ItemPreview = HistoryView::ItemPreview;
@ -178,6 +179,57 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
return flags; return flags;
} }
using OnStackUsers = std::array<UserData*, kMaxUnreadReactions>;
[[nodiscard]] OnStackUsers LookupRecentReactedUsers(
not_null<HistoryItem*> item) {
auto result = OnStackUsers();
auto index = 0;
for (const auto &[emoji, reactions] : item->recentReactions()) {
for (const auto &reaction : reactions) {
if (const auto user = reaction.peer->asUser()) {
result[index++] = user;
if (index == result.size()) {
return result;
}
}
}
}
return result;
}
void CheckReactionNotificationSchedule(
not_null<HistoryItem*> item,
const OnStackUsers &wasUsers) {
if (!item->hasUnreadReaction()) {
return;
}
for (const auto &[emoji, reactions] : item->recentReactions()) {
for (const auto &reaction : reactions) {
if (!reaction.unread) {
continue;
}
const auto user = reaction.peer->asUser();
if (!user
|| !user->isContact()
|| ranges::contains(wasUsers, user)) {
continue;
}
using Status = PeerData::BlockStatus;
if (user->blockStatus() == Status::Unknown) {
user->updateFull();
}
const auto notification = ItemNotification{
.item = item,
.reactionSender = user,
.type = ItemNotificationType::Reaction,
};
item->history()->pushNotification(notification);
Core::App().notifications().schedule(notification);
return;
}
}
}
} // namespace } // namespace
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
@ -846,6 +898,7 @@ void HistoryItem::toggleReaction(const QString &reaction) {
} }
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
const auto wasRecentUsers = LookupRecentReactedUsers(this);
const auto hadUnread = hasUnreadReaction(); const auto hadUnread = hasUnreadReaction();
const auto changed = changeReactions(reactions); const auto changed = changeReactions(reactions);
if (!changed) { if (!changed) {
@ -859,16 +912,11 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
// Call to addToUnreadThings may have read the reaction already. // Call to addToUnreadThings may have read the reaction already.
if (hasUnreadReaction()) { if (hasUnreadReaction()) {
const auto notification = ItemNotification{
this,
ItemNotificationType::Reaction,
};
history()->pushNotification(notification);
Core::App().notifications().schedule(notification);
} }
} else if (!hasUnread && hadUnread) { } else if (!hasUnread && hadUnread) {
markReactionsRead(); markReactionsRead();
} }
CheckReactionNotificationSchedule(this, wasRecentUsers);
history()->owner().notifyItemDataChange(this); history()->owner().notifyItemDataChange(this);
} }
@ -932,21 +980,21 @@ QString HistoryItem::chosenReaction() const {
return _reactions ? _reactions->chosen() : QString(); return _reactions ? _reactions->chosen() : QString();
} }
HistoryItemUnreadReaction HistoryItem::lookupUnreadReaction() const { QString HistoryItem::lookupUnreadReaction(not_null<UserData*> from) const {
if (!_reactions) { if (!_reactions) {
return {}; return QString();
} }
const auto recent = _reactions->recent(); const auto recent = _reactions->recent();
for (const auto &[emoji, list] : _reactions->recent()) { for (const auto &[emoji, list] : _reactions->recent()) {
const auto i = ranges::find( const auto i = ranges::find(
list, list,
true, from,
&Data::RecentReaction::unread); &Data::RecentReaction::peer);
if (i != end(list)) { if (i != end(list) && i->unread) {
return { .from = i->peer, .emoji = emoji }; return emoji;
} }
} }
return {}; return QString();
} }
crl::time HistoryItem::lastReactionsRefreshTime() const { crl::time HistoryItem::lastReactionsRefreshTime() const {

View file

@ -63,15 +63,6 @@ enum class Context : char;
class ElementDelegate; class ElementDelegate;
} // namespace HistoryView } // namespace HistoryView
struct HistoryItemUnreadReaction {
PeerData *from = nullptr;
QString emoji;
explicit operator bool() const {
return (from != nullptr) && !emoji.isEmpty();
}
};
struct HiddenSenderInfo; struct HiddenSenderInfo;
class History; class History;
@ -382,7 +373,8 @@ public:
std::vector<Data::RecentReaction>> &; std::vector<Data::RecentReaction>> &;
[[nodiscard]] bool canViewReactions() const; [[nodiscard]] bool canViewReactions() const;
[[nodiscard]] QString chosenReaction() const; [[nodiscard]] QString chosenReaction() const;
[[nodiscard]] HistoryItemUnreadReaction lookupUnreadReaction() const; [[nodiscard]] QString lookupUnreadReaction(
not_null<UserData*> from) const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const; [[nodiscard]] crl::time lastReactionsRefreshTime() const;
[[nodiscard]] bool hasDirectLink() const; [[nodiscard]] bool hasDirectLink() const;

View file

@ -68,6 +68,13 @@ QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) {
} // namespace } // namespace
struct System::Waiter {
NotificationInHistoryKey key;
UserData *reactionSender = nullptr;
ItemNotificationType type = ItemNotificationType::Message;
crl::time when = 0;
};
System::NotificationInHistoryKey::NotificationInHistoryKey( System::NotificationInHistoryKey::NotificationInHistoryKey(
ItemNotification notification) ItemNotification notification)
: NotificationInHistoryKey(notification.item->id, notification.type) { : NotificationInHistoryKey(notification.item->id, notification.type) {
@ -155,24 +162,30 @@ System::SkipState System::skipNotification(
&& skipReactionNotification(item))) { && skipReactionNotification(item))) {
return { SkipState::Skip }; return { SkipState::Skip };
} }
const auto showForMuted = messageNotification return computeSkipState(notification);
&& item->out()
&& item->isFromScheduled();
auto result = computeSkipState(item, showForMuted);
if (showForMuted) {
result.alsoMuted = true;
}
if (messageNotification && item->isSilent()) {
result.silent = true;
}
return result;
} }
System::SkipState System::computeSkipState( System::SkipState System::computeSkipState(
not_null<HistoryItem*> item, ItemNotification notification) const {
bool showForMuted) const { const auto type = notification.type;
const auto item = notification.item;
const auto history = item->history(); const auto history = item->history();
const auto notifyBy = item->specialNotificationPeer(); const auto messageNotification = (type == ItemNotificationType::Message);
const auto withSilent = [&](
SkipState::Value value,
bool forceSilent = false) {
return SkipState{
.value = value,
.silent = (forceSilent
|| (messageNotification && item->isSilent())),
};
};
const auto showForMuted = messageNotification
&& item->out()
&& item->isFromScheduled();
const auto notifyBy = messageNotification
? item->specialNotificationPeer()
: notification.reactionSender;
if (Core::Quitting()) { if (Core::Quitting()) {
return { SkipState::Skip }; return { SkipState::Skip };
} else if (!Core::App().settings().notifyFromAll() } else if (!Core::App().settings().notifyFromAll()
@ -180,29 +193,36 @@ System::SkipState System::computeSkipState(
return { SkipState::Skip }; return { SkipState::Skip };
} }
history->owner().requestNotifySettings(history->peer); if (messageNotification) {
history->owner().requestNotifySettings(history->peer);
} else if (notifyBy->blockStatus() == PeerData::BlockStatus::Unknown) {
notifyBy->updateFull();
}
if (notifyBy) { if (notifyBy) {
history->owner().requestNotifySettings(notifyBy); history->owner().requestNotifySettings(notifyBy);
} }
if (history->owner().notifyMuteUnknown(history->peer)) { if (messageNotification
&& history->owner().notifyMuteUnknown(history->peer)) {
return { SkipState::Unknown }; return { SkipState::Unknown };
} else if (!history->owner().notifyIsMuted(history->peer)) { } else if (messageNotification
return { SkipState::DontSkip }; && !history->owner().notifyIsMuted(history->peer)) {
return withSilent(SkipState::DontSkip);
} else if (!notifyBy) { } else if (!notifyBy) {
return { return withSilent(
showForMuted ? SkipState::DontSkip : SkipState::Skip, showForMuted ? SkipState::DontSkip : SkipState::Skip,
showForMuted, showForMuted);
}; } else if (history->owner().notifyMuteUnknown(notifyBy)
} else if (history->owner().notifyMuteUnknown(notifyBy)) { || (!messageNotification
return { SkipState::Unknown, item->isSilent() }; && notifyBy->blockStatus() == PeerData::BlockStatus::Unknown)) {
} else if (!history->owner().notifyIsMuted(notifyBy)) { return withSilent(SkipState::Unknown);
return { SkipState::DontSkip, item->isSilent() }; } else if (!history->owner().notifyIsMuted(notifyBy)
&& (messageNotification || !notifyBy->isBlocked())) {
return withSilent(SkipState::DontSkip);
} else { } else {
return { return withSilent(
showForMuted ? SkipState::DontSkip : SkipState::Skip, showForMuted ? SkipState::DontSkip : SkipState::Skip,
showForMuted, showForMuted);
};
} }
} }
@ -248,7 +268,9 @@ void System::schedule(ItemNotification notification) {
? kMinimalForwardDelay ? kMinimalForwardDelay
: kMinimalDelay; : kMinimalDelay;
const auto timing = countTiming(history, minimalDelay); const auto timing = countTiming(history, minimalDelay);
const auto notifyBy = item->specialNotificationPeer(); const auto notifyBy = (type == ItemNotificationType::Message)
? item->specialNotificationPeer()
: notification.reactionSender;
if (!skip.silent) { if (!skip.silent) {
_whenAlerts[history].emplace(timing.when, notifyBy); _whenAlerts[history].emplace(timing.when, notifyBy);
} }
@ -265,9 +287,9 @@ void System::schedule(ItemNotification notification) {
if (it == addTo.end() || it->second.when > timing.when) { if (it == addTo.end() || it->second.when > timing.when) {
addTo.emplace(history, Waiter{ addTo.emplace(history, Waiter{
.key = key, .key = key,
.reactionSender = notification.reactionSender,
.type = notification.type,
.when = timing.when, .when = timing.when,
.notifyBy = notifyBy,
.alsoMuted = skip.alsoMuted,
}); });
} }
} }
@ -367,41 +389,29 @@ void System::clearAllFast() {
void System::checkDelayed() { void System::checkDelayed() {
for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) { for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
const auto history = i->first; const auto remove = [&] {
const auto peer = history->peer; const auto history = i->first;
auto loaded = false; const auto peer = history->peer;
auto muted = false; const auto fullId = FullMsgId(peer->id, i->second.key.messageId);
if (!peer->owner().notifyMuteUnknown(peer)) { const auto item = peer->owner().message(fullId);
if (!peer->owner().notifyIsMuted(peer)) { if (!item) {
loaded = true; return true;
} else if (const auto from = i->second.notifyBy) {
if (!peer->owner().notifyMuteUnknown(from)) {
if (!peer->owner().notifyIsMuted(from)) {
loaded = true;
} else {
loaded = muted = true;
}
}
} else {
loaded = muted = true;
} }
} const auto state = computeSkipState({
if (loaded) { .item = item,
const auto fullId = FullMsgId( .reactionSender = i->second.reactionSender,
history->peer->id, .type = i->second.type,
i->second.key.messageId); });
if (const auto item = peer->owner().message(fullId)) { if (state.value == SkipState::Skip) {
if (!item->notificationReady()) { return true;
loaded = false; } else if (state.value == SkipState::Unknown
} || !item->notificationReady()) {
} else { return false;
muted = true;
}
}
if (loaded) {
if (!muted || i->second.alsoMuted) {
_waiters.emplace(i->first, i->second);
} }
_waiters.emplace(i->first, i->second);
return true;
}();
if (remove) {
i = _settingWaiters.erase(i); i = _settingWaiters.erase(i);
} else { } else {
++i; ++i;
@ -657,14 +667,14 @@ void System::showNext() {
const auto reactionNotification const auto reactionNotification
= (notify->type == ItemNotificationType::Reaction); = (notify->type == ItemNotificationType::Reaction);
const auto reaction = reactionNotification const auto reaction = reactionNotification
? notify->item->lookupUnreadReaction() ? notify->item->lookupUnreadReaction(notify->reactionSender)
: HistoryItemUnreadReaction(); : QString();
if (!reactionNotification || reaction) { if (!reactionNotification || !reaction.isEmpty()) {
_manager->showNotification({ _manager->showNotification({
.item = notify->item, .item = notify->item,
.forwardedCount = forwardedCount, .forwardedCount = forwardedCount,
.reactionFrom = reaction.from, .reactionFrom = notify->reactionSender,
.reactionEmoji = reaction.emoji, .reactionEmoji = reaction,
}); });
} }
} }

View file

@ -103,6 +103,8 @@ public:
} }
private: private:
struct Waiter;
struct SkipState { struct SkipState {
enum Value { enum Value {
Unknown, Unknown,
@ -111,7 +113,6 @@ private:
}; };
Value value = Value::Unknown; Value value = Value::Unknown;
bool silent = false; bool silent = false;
bool alsoMuted = false;
}; };
struct NotificationInHistoryKey { struct NotificationInHistoryKey {
NotificationInHistoryKey(ItemNotification notification); NotificationInHistoryKey(ItemNotification notification);
@ -127,12 +128,6 @@ private:
< std::pair(b.messageId, b.type); < std::pair(b.messageId, b.type);
} }
}; };
struct Waiter {
NotificationInHistoryKey key;
crl::time when = 0;
PeerData *notifyBy = nullptr;
bool alsoMuted = false;
};
struct Timing { struct Timing {
crl::time delay = 0; crl::time delay = 0;
crl::time when = 0; crl::time when = 0;
@ -152,8 +147,7 @@ private:
[[nodiscard]] SkipState skipNotification( [[nodiscard]] SkipState skipNotification(
ItemNotification notification) const; ItemNotification notification) const;
[[nodiscard]] SkipState computeSkipState( [[nodiscard]] SkipState computeSkipState(
not_null<HistoryItem*> item, ItemNotification notification) const;
bool showForMuted) const;
[[nodiscard]] Timing countTiming( [[nodiscard]] Timing countTiming(
not_null<History*> history, not_null<History*> history,
crl::time minimalDelay) const; crl::time minimalDelay) const;