From 90191819bd9a686bacd15696e2f812b75348467c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Mar 2026 19:46:10 +0300 Subject: [PATCH] Added api support of notify settings for reactions and poll votes. --- Telegram/CMakeLists.txt | 2 + .../api/api_reactions_notify_settings.cpp | 185 ++++++++++++++++++ .../api/api_reactions_notify_settings.h | 65 ++++++ Telegram/SourceFiles/apiwrap.cpp | 8 + Telegram/SourceFiles/apiwrap.h | 3 + .../history/history_item_helpers.cpp | 26 ++- .../window/notifications_manager.cpp | 8 +- 7 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_reactions_notify_settings.cpp create mode 100644 Telegram/SourceFiles/api/api_reactions_notify_settings.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 168b3c3055..6fc50700a6 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -165,6 +165,8 @@ PRIVATE api/api_premium.h api/api_premium_option.cpp api/api_premium_option.h + api/api_reactions_notify_settings.cpp + api/api_reactions_notify_settings.h api/api_read_metrics.cpp api/api_read_metrics.h api/api_report.cpp diff --git a/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp b/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp new file mode 100644 index 0000000000..e28d52ff02 --- /dev/null +++ b/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp @@ -0,0 +1,185 @@ +/* +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_reactions_notify_settings.h" + +#include "apiwrap.h" +#include "main/main_session.h" + +namespace Api { +namespace { + +[[nodiscard]] ReactionsNotifyFrom ParseFrom( + const MTPReactionNotificationsFrom &from) { + return from.match([](const MTPDreactionNotificationsFromContacts &) { + return ReactionsNotifyFrom::Contacts; + }, [](const MTPDreactionNotificationsFromAll &) { + return ReactionsNotifyFrom::All; + }); +} + +[[nodiscard]] MTPReactionNotificationsFrom SerializeFrom( + ReactionsNotifyFrom from) { + switch (from) { + case ReactionsNotifyFrom::Contacts: + return MTP_reactionNotificationsFromContacts(); + case ReactionsNotifyFrom::All: + return MTP_reactionNotificationsFromAll(); + } + Unexpected("Value in SerializeFrom."); +} + +} // namespace + +ReactionsNotifySettings::ReactionsNotifySettings(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +void ReactionsNotifySettings::reload(Fn callback) { + if (callback) { + _callbacks.push_back(std::move(callback)); + } + if (_requestId) { + return; + } + _requestId = _api.request(MTPaccount_GetReactionsNotifySettings( + )).done([=](const MTPReactionsNotifySettings &result) { + _requestId = 0; + apply(result); + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).fail([=] { + _requestId = 0; + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).send(); +} + +void ReactionsNotifySettings::updateMessagesFrom(ReactionsNotifyFrom value) { + _messagesFrom = value; + save(); +} + +void ReactionsNotifySettings::updatePollVotesFrom( + ReactionsNotifyFrom value) { + _pollVotesFrom = value; + save(); +} + +void ReactionsNotifySettings::setAllFrom(ReactionsNotifyFrom value) { + _messagesFrom = value; + _pollVotesFrom = value; + save(); +} + +void ReactionsNotifySettings::updateShowPreviews(bool value) { + _showPreviews = value; + save(); +} + +ReactionsNotifyFrom ReactionsNotifySettings::messagesFromCurrent() const { + return _messagesFrom.current(); +} + +rpl::producer ReactionsNotifySettings::messagesFrom() const { + return _messagesFrom.value(); +} + +ReactionsNotifyFrom ReactionsNotifySettings::pollVotesFromCurrent() const { + return _pollVotesFrom.current(); +} + +rpl::producer ReactionsNotifySettings::pollVotesFrom() const { + return _pollVotesFrom.value(); +} + +bool ReactionsNotifySettings::showPreviewsCurrent() const { + return _showPreviews.current(); +} + +rpl::producer ReactionsNotifySettings::showPreviews() const { + return _showPreviews.value(); +} + +bool ReactionsNotifySettings::enabledCurrent() const { + return (_messagesFrom.current() != ReactionsNotifyFrom::None) + || (_pollVotesFrom.current() != ReactionsNotifyFrom::None); +} + +rpl::producer ReactionsNotifySettings::enabled() const { + return rpl::combine( + _messagesFrom.value(), + _pollVotesFrom.value() + ) | rpl::map([]( + ReactionsNotifyFrom messages, + ReactionsNotifyFrom pollVotes) { + return (messages != ReactionsNotifyFrom::None) + || (pollVotes != ReactionsNotifyFrom::None); + }) | rpl::distinct_until_changed(); +} + +void ReactionsNotifySettings::apply( + const MTPReactionsNotifySettings &settings) { + const auto &data = settings.data(); + const auto messages = data.vmessages_notify_from(); + const auto stories = data.vstories_notify_from(); + const auto pollVotes = data.vpoll_votes_notify_from(); + _messagesFrom = messages + ? ParseFrom(*messages) + : ReactionsNotifyFrom::None; + _storiesFrom = stories + ? ParseFrom(*stories) + : ReactionsNotifyFrom::None; + _pollVotesFrom = pollVotes + ? ParseFrom(*pollVotes) + : ReactionsNotifyFrom::None; + _showPreviews = mtpIsTrue(data.vshow_previews()); +} + +void ReactionsNotifySettings::save() { + using Flag = MTPDreactionsNotifySettings::Flag; + const auto messages = _messagesFrom.current(); + const auto stories = _storiesFrom.current(); + const auto pollVotes = _pollVotesFrom.current(); + const auto previews = _showPreviews.current(); + const auto flags = Flag() + | ((messages != ReactionsNotifyFrom::None) + ? Flag::f_messages_notify_from + : Flag()) + | ((stories != ReactionsNotifyFrom::None) + ? Flag::f_stories_notify_from + : Flag()) + | ((pollVotes != ReactionsNotifyFrom::None) + ? Flag::f_poll_votes_notify_from + : Flag()); + _api.request(base::take(_requestId)).cancel(); + _requestId = _api.request(MTPaccount_SetReactionsNotifySettings( + MTP_reactionsNotifySettings( + MTP_flags(flags), + ((messages != ReactionsNotifyFrom::None) + ? SerializeFrom(messages) + : MTPReactionNotificationsFrom()), + ((stories != ReactionsNotifyFrom::None) + ? SerializeFrom(stories) + : MTPReactionNotificationsFrom()), + ((pollVotes != ReactionsNotifyFrom::None) + ? SerializeFrom(pollVotes) + : MTPReactionNotificationsFrom()), + MTP_notificationSoundDefault(), + MTP_bool(previews)) + )).done([=](const MTPReactionsNotifySettings &result) { + _requestId = 0; + apply(result); + }).fail([=] { + _requestId = 0; + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_reactions_notify_settings.h b/Telegram/SourceFiles/api/api_reactions_notify_settings.h new file mode 100644 index 0000000000..d29a1a6787 --- /dev/null +++ b/Telegram/SourceFiles/api/api_reactions_notify_settings.h @@ -0,0 +1,65 @@ +/* +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 +*/ +#pragma once + +#include "mtproto/sender.h" + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +enum class ReactionsNotifyFrom : uchar { + None, + Contacts, + All, +}; + +class ReactionsNotifySettings final { +public: + explicit ReactionsNotifySettings(not_null api); + + void reload(Fn callback = nullptr); + + void updateMessagesFrom(ReactionsNotifyFrom value); + void updatePollVotesFrom(ReactionsNotifyFrom value); + void setAllFrom(ReactionsNotifyFrom value); + void updateShowPreviews(bool value); + + [[nodiscard]] ReactionsNotifyFrom messagesFromCurrent() const; + [[nodiscard]] rpl::producer messagesFrom() const; + [[nodiscard]] ReactionsNotifyFrom pollVotesFromCurrent() const; + [[nodiscard]] rpl::producer pollVotesFrom() const; + [[nodiscard]] bool showPreviewsCurrent() const; + [[nodiscard]] rpl::producer showPreviews() const; + + [[nodiscard]] bool enabledCurrent() const; + [[nodiscard]] rpl::producer enabled() const; + +private: + void apply(const MTPReactionsNotifySettings &settings); + void save(); + + const not_null _session; + MTP::Sender _api; + mtpRequestId _requestId = 0; + rpl::variable _messagesFrom + = ReactionsNotifyFrom::All; + rpl::variable _storiesFrom + = ReactionsNotifyFrom::All; + rpl::variable _pollVotesFrom + = ReactionsNotifyFrom::All; + rpl::variable _showPreviews = true; + std::vector> _callbacks; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index f6181512ee..ccc2891b19 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_self_destruct.h" #include "api/api_sensitive_content.h" #include "api/api_global_privacy.h" +#include "api/api_reactions_notify_settings.h" #include "api/api_updates.h" #include "api/api_user_privacy.h" #include "api/api_read_metrics.h" @@ -179,6 +180,8 @@ ApiWrap::ApiWrap(not_null session) , _selfDestruct(std::make_unique(this)) , _sensitiveContent(std::make_unique(this)) , _globalPrivacy(std::make_unique(this)) +, _reactionsNotifySettings( + std::make_unique(this)) , _userPrivacy(std::make_unique(this)) , _inviteLinks(std::make_unique(this)) , _chatLinks(std::make_unique(this)) @@ -207,6 +210,7 @@ ApiWrap::ApiWrap(not_null session) requestMoreDialogsIfNeeded(); }, _session->lifetime()); + _reactionsNotifySettings->reload(); setupSupportMode(); }); } @@ -4968,6 +4972,10 @@ Api::GlobalPrivacy &ApiWrap::globalPrivacy() { return *_globalPrivacy; } +Api::ReactionsNotifySettings &ApiWrap::reactionsNotifySettings() { + return *_reactionsNotifySettings; +} + Api::UserPrivacy &ApiWrap::userPrivacy() { return *_userPrivacy; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 6f775ad193..c57e4976d8 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -69,6 +69,7 @@ class CloudPassword; class SelfDestruct; class SensitiveContent; class GlobalPrivacy; +class ReactionsNotifySettings; class UserPrivacy; class InviteLinks; class ChatLinks; @@ -412,6 +413,7 @@ public: [[nodiscard]] Api::SelfDestruct &selfDestruct(); [[nodiscard]] Api::SensitiveContent &sensitiveContent(); [[nodiscard]] Api::GlobalPrivacy &globalPrivacy(); + [[nodiscard]] Api::ReactionsNotifySettings &reactionsNotifySettings(); [[nodiscard]] Api::UserPrivacy &userPrivacy(); [[nodiscard]] Api::InviteLinks &inviteLinks(); [[nodiscard]] Api::ChatLinks &chatLinks(); @@ -772,6 +774,7 @@ private: const std::unique_ptr _selfDestruct; const std::unique_ptr _sensitiveContent; const std::unique_ptr _globalPrivacy; + const std::unique_ptr _reactionsNotifySettings; const std::unique_ptr _userPrivacy; const std::unique_ptr _inviteLinks; const std::unique_ptr _chatLinks; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 665d7d0401..4da07e71f4 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_helpers.h" +#include "api/api_reactions_notify_settings.h" #include "api/api_text_entities.h" #include "boxes/premium_preview_box.h" #include "calls/calls_instance.h" @@ -1175,15 +1176,22 @@ void CheckReactionNotificationSchedule( if (!item->hasUnreadReaction()) { return; } + const auto from = item->history()->session().api() + .reactionsNotifySettings().messagesFromCurrent(); + if (from == Api::ReactionsNotifyFrom::None) { + 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)) { + if (!user || ranges::contains(wasUsers, user)) { + continue; + } + if (from == Api::ReactionsNotifyFrom::Contacts + && !user->isContact()) { continue; } using Status = PeerData::BlockStatus; @@ -1210,11 +1218,19 @@ void CheckPollVoteNotificationSchedule( if (!poll || !poll->creator()) { return; } + const auto from = item->history()->session().api() + .reactionsNotifySettings().pollVotesFromCurrent(); + if (from == Api::ReactionsNotifyFrom::None) { + return; + } for (const auto &answer : poll->answers) { for (const auto &voter : answer.recentVoters) { const auto user = voter->asUser(); - if (!user - || ranges::contains(wasRecentVoters, voter)) { + if (!user || ranges::contains(wasRecentVoters, voter)) { + continue; + } + if (from == Api::ReactionsNotifyFrom::Contacts + && !user->isContact()) { continue; } using Status = PeerData::BlockStatus; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index a54ed809ba..6ab49499a5 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "core/application.h" #include "mainwindow.h" +#include "api/api_reactions_notify_settings.h" #include "api/api_updates.h" #include "apiwrap.h" #include "main/main_account.h" @@ -1482,8 +1483,13 @@ void NativeManager::doShowNotification(NotificationFields &&fields) { ? tr::lng_notification_reminder(tr::now) : subWithChat(); const auto fullTitle = addTargetAccountName(title, &peer->session()); + const auto hideReactionSender = reactionFrom + && !peer->session().api().reactionsNotifySettings() + .showPreviewsCurrent(); const auto subtitle = reactionFrom - ? (reactionFrom != peer ? reactionFrom->name() : QString()) + ? ((!hideReactionSender && reactionFrom != peer) + ? reactionFrom->name() + : QString()) : options.hideNameAndPhoto ? QString() : item->notificationHeader();