// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/AudiosManager.hpp" #include "td/telegram/AuthManager.h" #include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/NotificationManager.h" #include "td/telegram/NotificationSound.h" #include "td/telegram/OptionManager.h" #include "td/telegram/ReactionNotificationSettings.hpp" #include "td/telegram/ScopeNotificationSettings.hpp" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserManager.h" #include "td/telegram/VoiceNotesManager.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/MimeType.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/Random.h" #include "td/utils/tl_helpers.h" #include namespace td { class UploadRingtoneQuery final : public Td::ResultHandler { FileId file_id_; Promise> promise_; public: explicit UploadRingtoneQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(FileId file_id, tl_object_ptr &&input_file, const string &file_name, const string &mime_type) { CHECK(input_file != nullptr); file_id_ = file_id; send_query(G()->net_query_creator().create( telegram_api::account_uploadRingtone(std::move(input_file), file_name, mime_type), {{"ringtone"}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UploadRingtoneQuery: " << to_string(result); promise_.set_value(std::move(result)); td_->file_manager_->delete_partial_remote_location(file_id_); } void on_error(Status status) final { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } auto bad_parts = FileManager::get_missing_file_parts(status); if (!bad_parts.empty()) { // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); td_->notification_settings_manager_->reload_saved_ringtones(Auto()); promise_.set_error(std::move(status)); } }; class SaveRingtoneQuery final : public Td::ResultHandler { FileId file_id_; string file_reference_; bool unsave_ = false; Promise> promise_; public: explicit SaveRingtoneQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(FileId file_id, tl_object_ptr &&input_document, bool unsave) { CHECK(input_document != nullptr); CHECK(file_id.is_valid()); file_id_ = file_id; file_reference_ = input_document->file_reference_.as_slice().str(); unsave_ = unsave; send_query(G()->net_query_creator().create(telegram_api::account_saveRingtone(std::move(input_document), unsave), {{"ringtone"}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SaveRingtoneQuery: " << to_string(result); promise_.set_value(std::move(result)); } void on_error(Status status) final { if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { VLOG(file_references) << "Receive " << status << " for " << file_id_; td_->file_manager_->delete_file_reference(file_id_, file_reference_); td_->file_reference_manager_->repair_file_reference( file_id_, PromiseCreator::lambda([ringtone_id = file_id_, unsave = unsave_, promise = std::move(promise_)](Result result) mutable { if (result.is_error()) { return promise.set_error(Status::Error(400, "Failed to find the ringtone")); } send_closure(G()->notification_settings_manager(), &NotificationSettingsManager::send_save_ringtone_query, ringtone_id, unsave, std::move(promise)); })); return; } if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for SaveRingtoneQuery: " << status; } td_->notification_settings_manager_->reload_saved_ringtones(Auto()); promise_.set_error(std::move(status)); } }; class GetSavedRingtonesQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetSavedRingtonesQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(int64 hash) { send_query(G()->net_query_creator().create(telegram_api::account_getSavedRingtones(hash), {{"ringtone"}})); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetSavedRingtonesQuery: " << to_string(ptr); promise_.set_value(std::move(ptr)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetDialogNotifySettingsQuery final : public Td::ResultHandler { DialogId dialog_id_; MessageId top_thread_message_id_; public: void send(DialogId dialog_id, MessageId top_thread_message_id) { dialog_id_ = dialog_id; top_thread_message_id_ = top_thread_message_id; auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_id, top_thread_message_id); CHECK(input_notify_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::account_getNotifySettings(std::move(input_notify_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); if (top_thread_message_id_.is_valid()) { td_->forum_topic_manager_->on_update_forum_topic_notify_settings(dialog_id_, top_thread_message_id_, std::move(ptr), "GetDialogNotifySettingsQuery"); } else { td_->messages_manager_->on_update_dialog_notify_settings(dialog_id_, std::move(ptr), "GetDialogNotifySettingsQuery"); } td_->notification_settings_manager_->on_get_dialog_notification_settings_query_finished( dialog_id_, top_thread_message_id_, Status::OK()); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogNotifySettingsQuery"); td_->notification_settings_manager_->on_get_dialog_notification_settings_query_finished( dialog_id_, top_thread_message_id_, std::move(status)); } }; class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetNotifySettingsExceptionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(NotificationSettingsScope scope, bool filter_scope, bool compare_sound) { int32 flags = 0; tl_object_ptr input_notify_peer; if (filter_scope) { flags |= telegram_api::account_getNotifyExceptions::PEER_MASK; input_notify_peer = get_input_notify_peer(scope); } if (compare_sound) { flags |= telegram_api::account_getNotifyExceptions::COMPARE_SOUND_MASK; } send_query(G()->net_query_creator().create(telegram_api::account_getNotifyExceptions( flags, false /*ignored*/, false /*ignored*/, std::move(input_notify_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto updates_ptr = result_ptr.move_as_ok(); auto dialog_ids = UpdatesManager::get_update_notify_settings_dialog_ids(updates_ptr.get()); vector> users; vector> chats; switch (updates_ptr->get_id()) { case telegram_api::updatesCombined::ID: { auto updates = static_cast(updates_ptr.get()); users = std::move(updates->users_); chats = std::move(updates->chats_); reset_to_empty(updates->users_); reset_to_empty(updates->chats_); break; } case telegram_api::updates::ID: { auto updates = static_cast(updates_ptr.get()); users = std::move(updates->users_); chats = std::move(updates->chats_); reset_to_empty(updates->users_); reset_to_empty(updates->chats_); break; } } td_->user_manager_->on_get_users(std::move(users), "GetNotifySettingsExceptionsQuery"); td_->chat_manager_->on_get_chats(std::move(chats), "GetNotifySettingsExceptionsQuery"); for (auto &dialog_id : dialog_ids) { td_->dialog_manager_->force_create_dialog(dialog_id, "GetNotifySettingsExceptionsQuery"); } td_->updates_manager_->on_get_updates(std::move(updates_ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetStoryNotifySettingsExceptionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStoryNotifySettingsExceptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send() { int32 flags = telegram_api::account_getNotifyExceptions::COMPARE_STORIES_MASK; send_query(G()->net_query_creator().create( telegram_api::account_getNotifyExceptions(flags, false /*ignored*/, false /*ignored*/, nullptr))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto updates_ptr = result_ptr.move_as_ok(); auto dialog_ids = UpdatesManager::get_update_notify_settings_dialog_ids(updates_ptr.get()); vector> users; vector> chats; switch (updates_ptr->get_id()) { case telegram_api::updatesCombined::ID: { auto updates = static_cast(updates_ptr.get()); users = std::move(updates->users_); chats = std::move(updates->chats_); reset_to_empty(updates->users_); reset_to_empty(updates->chats_); break; } case telegram_api::updates::ID: { auto updates = static_cast(updates_ptr.get()); users = std::move(updates->users_); chats = std::move(updates->chats_); reset_to_empty(updates->users_); reset_to_empty(updates->chats_); break; } } td_->user_manager_->on_get_users(std::move(users), "GetStoryNotifySettingsExceptionsQuery"); td_->chat_manager_->on_get_chats(std::move(chats), "GetStoryNotifySettingsExceptionsQuery"); for (auto &dialog_id : dialog_ids) { td_->dialog_manager_->force_create_dialog(dialog_id, "GetStoryNotifySettingsExceptionsQuery"); } auto chat_ids = td_->dialog_manager_->get_chats_object(-1, dialog_ids, "GetStoryNotifySettingsExceptionsQuery"); auto promise = PromiseCreator::lambda([promise = std::move(promise_), chat_ids = std::move(chat_ids)]( Result) mutable { promise.set_value(std::move(chat_ids)); }); td_->updates_manager_->on_get_updates(std::move(updates_ptr), std::move(promise)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetScopeNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; NotificationSettingsScope scope_; public: explicit GetScopeNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(NotificationSettingsScope scope) { scope_ = scope; auto input_notify_peer = get_input_notify_peer(scope); CHECK(input_notify_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::account_getNotifySettings(std::move(input_notify_peer)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); td_->notification_settings_manager_->on_update_scope_notify_settings(scope_, std::move(ptr)); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetReactionsNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; public: explicit GetReactionsNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::account_getReactionsNotifySettings())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); td_->notification_settings_manager_->on_update_reaction_notification_settings( ReactionNotificationSettings(std::move(ptr))); promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class UpdateDialogNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; MessageId top_thread_message_id_; public: explicit UpdateDialogNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, MessageId top_thread_message_id, const DialogNotificationSettings &new_settings) { dialog_id_ = dialog_id; top_thread_message_id_ = top_thread_message_id; auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_id, top_thread_message_id); if (input_notify_peer == nullptr) { return on_error(Status::Error(500, "Can't update chat notification settings")); } send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings( std::move(input_notify_peer), new_settings.get_input_peer_notify_settings()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Receive false as result")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "UpdateDialogNotifySettingsQuery")) { LOG(INFO) << "Receive error for set chat notification settings: " << status; } if (!td_->auth_manager_->is_bot() && td_->notification_settings_manager_->get_input_notify_peer(dialog_id_, top_thread_message_id_) != nullptr) { // trying to repair notification settings for this dialog td_->notification_settings_manager_->send_get_dialog_notification_settings_query( dialog_id_, top_thread_message_id_, Promise<>()); } promise_.set_error(std::move(status)); } }; class UpdateScopeNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; NotificationSettingsScope scope_; public: explicit UpdateScopeNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(NotificationSettingsScope scope, const ScopeNotificationSettings &new_settings) { auto input_notify_peer = get_input_notify_peer(scope); CHECK(input_notify_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings( std::move(input_notify_peer), new_settings.get_input_peer_notify_settings()))); scope_ = scope; } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Receive false as result")); } promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for set notification settings: " << status; if (!td_->auth_manager_->is_bot()) { // trying to repair notification settings for this scope td_->notification_settings_manager_->send_get_scope_notification_settings_query(scope_, Promise<>()); } promise_.set_error(std::move(status)); } }; class SetReactionsNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; public: explicit SetReactionsNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const ReactionNotificationSettings &settings) { send_query(G()->net_query_creator().create( telegram_api::account_setReactionsNotifySettings(settings.get_input_reactions_notify_settings()))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SetReactionsNotifySettingsQuery: " << to_string(ptr); promise_.set_value(Unit()); } void on_error(Status status) final { LOG(INFO) << "Receive error for set reaction notification settings: " << status; if (!td_->auth_manager_->is_bot()) { // trying to repair notification settings td_->notification_settings_manager_->send_get_reaction_notification_settings_query(Promise<>()); } promise_.set_error(std::move(status)); } }; class ResetNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; public: explicit ResetNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::account_resetNotifySettings())); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); if (!result) { return on_error(Status::Error(400, "Receive false as result")); } promise_.set_value(Unit()); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for reset notification settings: " << status; } promise_.set_error(std::move(status)); } }; class NotificationSettingsManager::UploadRingtoneCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { send_closure_later(G()->notification_settings_manager(), &NotificationSettingsManager::on_upload_ringtone, file_id, std::move(input_file)); } void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { UNREACHABLE(); } void on_upload_error(FileId file_id, Status error) final { send_closure_later(G()->notification_settings_manager(), &NotificationSettingsManager::on_upload_ringtone_error, file_id, std::move(error)); } }; class NotificationSettingsManager::RingtoneListLogEvent { public: int64 hash_; vector ringtone_file_ids_; RingtoneListLogEvent() = default; RingtoneListLogEvent(int64 hash, vector ringtone_file_ids) : hash_(hash), ringtone_file_ids_(std::move(ringtone_file_ids)) { } template void store(StorerT &storer) const { td::store(hash_, storer); AudiosManager *audios_manager = storer.context()->td().get_actor_unsafe()->audios_manager_.get(); td::store(narrow_cast(ringtone_file_ids_.size()), storer); for (auto ringtone_file_id : ringtone_file_ids_) { audios_manager->store_audio(ringtone_file_id, storer); } } template void parse(ParserT &parser) { td::parse(hash_, parser); AudiosManager *audios_manager = parser.context()->td().get_actor_unsafe()->audios_manager_.get(); int32 size = parser.fetch_int(); ringtone_file_ids_.resize(size); for (auto &ringtone_file_id : ringtone_file_ids_) { ringtone_file_id = audios_manager->parse_audio(parser); } } }; NotificationSettingsManager::NotificationSettingsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_ringtone_callback_ = std::make_shared(); scope_unmute_timeout_.set_callback(on_scope_unmute_timeout_callback); scope_unmute_timeout_.set_callback_data(static_cast(this)); } NotificationSettingsManager::~NotificationSettingsManager() = default; void NotificationSettingsManager::tear_down() { parent_.reset(); } void NotificationSettingsManager::start_up() { init(); } void NotificationSettingsManager::init() { if (is_inited_) { return; } is_inited_ = true; bool is_authorized = td_->auth_manager_->is_authorized(); bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot(); if (was_authorized_user) { for (auto scope : {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) { auto notification_settings_string = G()->td_db()->get_binlog_pmc()->get(get_notification_settings_scope_database_key(scope)); if (!notification_settings_string.empty()) { auto current_settings = get_scope_notification_settings(scope); CHECK(current_settings != nullptr); log_event_parse(*current_settings, notification_settings_string).ensure(); VLOG(notifications) << "Loaded notification settings in " << scope << ": " << *current_settings; schedule_scope_unmute(scope, current_settings->mute_until, G()->unix_time()); send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope)); } } if (!channels_notification_settings_.is_synchronized && is_authorized) { channels_notification_settings_ = ScopeNotificationSettings( chats_notification_settings_.mute_until, dup_notification_sound(chats_notification_settings_.sound), chats_notification_settings_.show_preview, chats_notification_settings_.use_default_mute_stories, chats_notification_settings_.mute_stories, nullptr, false, false, false); channels_notification_settings_.is_synchronized = false; send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>()); } auto reaction_notification_settings_string = G()->td_db()->get_binlog_pmc()->get(get_reaction_notification_settings_database_key()); if (!reaction_notification_settings_string.empty()) { log_event_parse(reaction_notification_settings_, reaction_notification_settings_string).ensure(); have_reaction_notification_settings_ = true; VLOG(notifications) << "Loaded reaction notification settings: " << reaction_notification_settings_; } else { send_get_reaction_notification_settings_query(Promise()); } send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); } G()->td_db()->get_binlog_pmc()->erase("nsfac"); } void NotificationSettingsManager::on_scope_unmute_timeout_callback(void *notification_settings_manager_ptr, int64 scope_int) { if (G()->close_flag()) { return; } CHECK(1 <= scope_int && scope_int <= 3); auto notification_settings_manager = static_cast(notification_settings_manager_ptr); send_closure_later(notification_settings_manager->actor_id(notification_settings_manager), &NotificationSettingsManager::on_scope_unmute, static_cast(scope_int - 1)); } void NotificationSettingsManager::timeout_expired() { reload_saved_ringtones(Promise()); } int32 NotificationSettingsManager::get_scope_mute_until(NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->mute_until; } std::pair NotificationSettingsManager::get_scope_mute_stories(NotificationSettingsScope scope) const { auto *settings = get_scope_notification_settings(scope); return {settings->use_default_mute_stories, settings->mute_stories}; } const unique_ptr &NotificationSettingsManager::get_scope_notification_sound( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->sound; } const unique_ptr &NotificationSettingsManager::get_scope_story_notification_sound( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->story_sound; } bool NotificationSettingsManager::get_scope_show_preview(NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->show_preview; } bool NotificationSettingsManager::get_scope_hide_story_sender(NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->hide_story_sender; } bool NotificationSettingsManager::get_scope_disable_pinned_message_notifications( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->disable_pinned_message_notifications; } bool NotificationSettingsManager::get_scope_disable_mention_notifications(NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->disable_mention_notifications; } tl_object_ptr NotificationSettingsManager::get_input_notify_peer( DialogId dialog_id, MessageId top_thread_message_id) const { if (!td_->messages_manager_->have_dialog(dialog_id)) { return nullptr; } auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return nullptr; } if (top_thread_message_id.is_valid()) { CHECK(top_thread_message_id.is_server()); return telegram_api::make_object( std::move(input_peer), top_thread_message_id.get_server_message_id().get()); } return make_tl_object(std::move(input_peer)); } ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings( NotificationSettingsScope scope) { switch (scope) { case NotificationSettingsScope::Private: return &users_notification_settings_; case NotificationSettingsScope::Group: return &chats_notification_settings_; case NotificationSettingsScope::Channel: return &channels_notification_settings_; default: UNREACHABLE(); return nullptr; } } const ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings( NotificationSettingsScope scope) const { switch (scope) { case NotificationSettingsScope::Private: return &users_notification_settings_; case NotificationSettingsScope::Group: return &chats_notification_settings_; case NotificationSettingsScope::Channel: return &channels_notification_settings_; default: UNREACHABLE(); return nullptr; } } td_api::object_ptr NotificationSettingsManager::get_update_scope_notification_settings_object(NotificationSettingsScope scope) const { auto notification_settings = get_scope_notification_settings(scope); CHECK(notification_settings != nullptr); return td_api::make_object( get_notification_settings_scope_object(scope), get_scope_notification_settings_object(notification_settings)); } td_api::object_ptr NotificationSettingsManager::get_update_reaction_notification_settings_object() const { return td_api::make_object( reaction_notification_settings_.get_reaction_notification_settings_object()); } void NotificationSettingsManager::on_scope_unmute(NotificationSettingsScope scope) { if (td_->auth_manager_->is_bot()) { // just in case return; } auto notification_settings = get_scope_notification_settings(scope); CHECK(notification_settings != nullptr); if (notification_settings->mute_until == 0) { return; } auto unix_time = G()->unix_time(); if (notification_settings->mute_until > unix_time) { LOG(INFO) << "Failed to unmute " << scope << " in " << unix_time << ", will be unmuted in " << notification_settings->mute_until; schedule_scope_unmute(scope, notification_settings->mute_until, unix_time); return; } LOG(INFO) << "Unmute " << scope; update_scope_unmute_timeout(scope, notification_settings->mute_until, 0); send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope)); save_scope_notification_settings(scope, *notification_settings); } string NotificationSettingsManager::get_notification_settings_scope_database_key(NotificationSettingsScope scope) { switch (scope) { case NotificationSettingsScope::Private: return "nsfpc"; case NotificationSettingsScope::Group: return "nsfgc"; case NotificationSettingsScope::Channel: return "nsfcc"; default: UNREACHABLE(); return ""; } } void NotificationSettingsManager::save_scope_notification_settings(NotificationSettingsScope scope, const ScopeNotificationSettings &new_settings) { string key = get_notification_settings_scope_database_key(scope); G()->td_db()->get_binlog_pmc()->set(key, log_event_store(new_settings).as_slice().str()); } void NotificationSettingsManager::on_update_scope_notify_settings( NotificationSettingsScope scope, tl_object_ptr &&peer_notify_settings) { if (td_->auth_manager_->is_bot()) { return; } auto old_notification_settings = get_scope_notification_settings(scope); CHECK(old_notification_settings != nullptr); ScopeNotificationSettings notification_settings = ::td::get_scope_notification_settings( std::move(peer_notify_settings), old_notification_settings->disable_pinned_message_notifications, old_notification_settings->disable_mention_notifications); if (!notification_settings.is_synchronized) { return; } update_scope_notification_settings(scope, old_notification_settings, std::move(notification_settings)); } bool NotificationSettingsManager::update_scope_notification_settings(NotificationSettingsScope scope, ScopeNotificationSettings *current_settings, ScopeNotificationSettings &&new_settings) { if (td_->auth_manager_->is_bot()) { // just in case return false; } bool need_update_server = current_settings->mute_until != new_settings.mute_until || !are_equivalent_notification_sounds(current_settings->sound, new_settings.sound) || current_settings->show_preview != new_settings.show_preview; bool need_update_local = current_settings->disable_pinned_message_notifications != new_settings.disable_pinned_message_notifications || current_settings->disable_mention_notifications != new_settings.disable_mention_notifications; bool was_inited = current_settings->is_synchronized; bool is_inited = new_settings.is_synchronized; if (was_inited && !is_inited) { return false; // just in case } bool is_changed = need_update_server || need_update_local || was_inited != is_inited || are_different_equivalent_notification_sounds(current_settings->sound, new_settings.sound); if (is_changed) { save_scope_notification_settings(scope, new_settings); VLOG(notifications) << "Update notification settings in " << scope << " from " << *current_settings << " to " << new_settings; update_scope_unmute_timeout(scope, current_settings->mute_until, new_settings.mute_until); if (!current_settings->disable_pinned_message_notifications && new_settings.disable_pinned_message_notifications) { td_->messages_manager_->remove_scope_pinned_message_notifications(scope); } if (current_settings->disable_mention_notifications != new_settings.disable_mention_notifications) { td_->messages_manager_->on_update_scope_mention_notifications(scope, new_settings.disable_mention_notifications); } *current_settings = std::move(new_settings); send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope)); } return need_update_server; } void NotificationSettingsManager::send_get_reaction_notification_settings_query(Promise &&promise) { if (td_->auth_manager_->is_bot()) { LOG(ERROR) << "Can't get reaction notification settings"; return promise.set_error(Status::Error(500, "Wrong getReactionNotificationSettings query")); } td_->create_handler(std::move(promise))->send(); } void NotificationSettingsManager::on_update_reaction_notification_settings( ReactionNotificationSettings reaction_notification_settings) { CHECK(!td_->auth_manager_->is_bot()); if (reaction_notification_settings == reaction_notification_settings_) { if (!have_reaction_notification_settings_) { have_reaction_notification_settings_ = true; save_reaction_notification_settings(); } return; } VLOG(notifications) << "Update reaction notification settings from " << reaction_notification_settings_ << " to " << reaction_notification_settings; reaction_notification_settings_ = std::move(reaction_notification_settings); have_reaction_notification_settings_ = true; save_reaction_notification_settings(); send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); } string NotificationSettingsManager::get_reaction_notification_settings_database_key() { return "rns"; } void NotificationSettingsManager::save_reaction_notification_settings() const { string key = get_reaction_notification_settings_database_key(); G()->td_db()->get_binlog_pmc()->set(key, log_event_store(reaction_notification_settings_).as_slice().str()); } void NotificationSettingsManager::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until, int32 unix_time) { if (mute_until >= unix_time && mute_until < unix_time + 366 * 86400) { scope_unmute_timeout_.set_timeout_in(static_cast(scope) + 1, mute_until - unix_time + 1); } else { scope_unmute_timeout_.cancel_timeout(static_cast(scope) + 1); } } void NotificationSettingsManager::update_scope_unmute_timeout(NotificationSettingsScope scope, int32 &old_mute_until, int32 new_mute_until) { if (td_->auth_manager_->is_bot()) { // just in case return; } LOG(INFO) << "Update " << scope << " unmute timeout from " << old_mute_until << " to " << new_mute_until; if (old_mute_until == new_mute_until) { return; } CHECK(old_mute_until >= 0); schedule_scope_unmute(scope, new_mute_until, G()->unix_time()); auto was_muted = old_mute_until != 0; auto is_muted = new_mute_until != 0; old_mute_until = new_mute_until; if (was_muted != is_muted) { td_->messages_manager_->on_update_notification_scope_is_muted(scope, is_muted); } } void NotificationSettingsManager::reset_scope_notification_settings() { CHECK(!td_->auth_manager_->is_bot()); for (auto scope : {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) { auto current_settings = get_scope_notification_settings(scope); CHECK(current_settings != nullptr); ScopeNotificationSettings new_scope_settings; new_scope_settings.is_synchronized = true; update_scope_notification_settings(scope, current_settings, std::move(new_scope_settings)); } } bool NotificationSettingsManager::is_active() const { return !G()->close_flag() && td_->auth_manager_->is_authorized() && !td_->auth_manager_->is_bot(); } FileId NotificationSettingsManager::get_saved_ringtone(int64 ringtone_id, Promise &&promise) { if (!are_saved_ringtones_loaded_) { load_saved_ringtones(std::move(promise)); return {}; } promise.set_value(Unit()); for (auto &file_id : saved_ringtone_file_ids_) { auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); CHECK(file_view.get_type() == FileType::Ringtone); CHECK(file_view.has_remote_location()); if (file_view.remote_location().get_id() == ringtone_id) { return file_view.get_main_file_id(); } } return {}; } vector NotificationSettingsManager::get_saved_ringtones(Promise &&promise) { if (!are_saved_ringtones_loaded_) { load_saved_ringtones(std::move(promise)); return {}; } promise.set_value(Unit()); return saved_ringtone_file_ids_; } void NotificationSettingsManager::send_save_ringtone_query( FileId ringtone_file_id, bool unsave, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); // TODO log event auto file_view = td_->file_manager_->get_file_view(ringtone_file_id); CHECK(!file_view.empty()); CHECK(file_view.has_remote_location()); CHECK(file_view.remote_location().is_document()); CHECK(!file_view.remote_location().is_web()); td_->create_handler(std::move(promise)) ->send(ringtone_file_id, file_view.remote_location().as_input_document(), unsave); } void NotificationSettingsManager::add_saved_ringtone(td_api::object_ptr &&input_file, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (!are_saved_ringtones_loaded_) { load_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), input_file = std::move(input_file), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &NotificationSettingsManager::add_saved_ringtone, std::move(input_file), std::move(promise)); })); return; } TRY_RESULT_PROMISE(promise, file_id, td_->file_manager_->get_input_file_id(FileType::Ringtone, input_file, DialogId(), false, false)); auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); if (file_view.size() > td_->option_manager_->get_option_integer("notification_sound_size_max")) { return promise.set_error(Status::Error(400, "Notification sound file is too big")); } auto file_type = file_view.get_type(); int32 duration = 0; switch (file_type) { case FileType::Audio: duration = td_->audios_manager_->get_audio_duration(file_id); break; case FileType::VoiceNote: duration = td_->voice_notes_manager_->get_voice_note_duration(file_id); break; default: break; } if (duration > td_->option_manager_->get_option_integer("notification_sound_duration_max")) { return promise.set_error(Status::Error(400, "Notification sound is too long")); } if (file_view.has_remote_location() && !file_view.is_encrypted()) { CHECK(file_view.remote_location().is_document()); if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web document as notification sound")); } FileId ringtone_file_id = file_view.get_main_file_id(); if (file_type != FileType::Ringtone) { if (file_type != FileType::Audio && file_type != FileType::VoiceNote) { return promise.set_error(Status::Error(400, "Unsupported file specified")); } auto &remote = file_view.main_remote_location(); ringtone_file_id = td_->file_manager_->register_remote( FullRemoteFileLocation(FileType::Ringtone, remote.get_id(), remote.get_access_hash(), remote.get_dc_id(), remote.get_file_reference().str()), FileLocationSource::FromServer, DialogId(), file_view.size(), file_view.expected_size(), file_view.suggested_path()); } if (file_type != FileType::VoiceNote) { for (auto &saved_ringtone_file_id : saved_ringtone_file_ids_) { if (ringtone_file_id == saved_ringtone_file_id) { return promise.set_value(td_->audios_manager_->get_notification_sound_object(ringtone_file_id)); } } } send_save_ringtone_query( file_view.get_main_file_id(), false, PromiseCreator::lambda( [actor_id = actor_id(this), file_id = ringtone_file_id, promise = std::move(promise)]( Result> &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, result.move_as_ok(), std::move(promise)); } })); return; } file_id = td_->file_manager_->copy_file_id(file_id, FileType::Ringtone, DialogId(), "add_saved_ringtone"); upload_ringtone(td_->file_manager_->dup_file_id(file_id, "add_saved_ringtone"), false, std::move(promise)); } void NotificationSettingsManager::upload_ringtone(FileId file_id, bool is_reupload, Promise> &&promise, vector bad_parts) { CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload ringtone " << file_id; bool is_inserted = being_uploaded_ringtones_.emplace(file_id, UploadedRingtone{is_reupload, std::move(promise)}).second; CHECK(is_inserted); // TODO use force_reupload if is_reupload td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_ringtone_callback_, 32, 0); } void NotificationSettingsManager::on_upload_ringtone(FileId file_id, tl_object_ptr input_file) { LOG(INFO) << "File " << file_id << " has been uploaded"; auto it = being_uploaded_ringtones_.find(file_id); if (it == being_uploaded_ringtones_.end()) { // just in case, as in on_upload_media return; } bool is_reupload = it->second.is_reupload; auto promise = std::move(it->second.promise); being_uploaded_ringtones_.erase(it); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); CHECK(file_view.get_type() == FileType::Ringtone); if (input_file == nullptr && file_view.has_remote_location()) { if (file_view.main_remote_location().is_web()) { return promise.set_error(Status::Error(400, "Can't use web document as notification sound")); } if (is_reupload) { return promise.set_error(Status::Error(400, "Failed to reupload the file")); } send_save_ringtone_query( file_view.get_main_file_id(), false, PromiseCreator::lambda( [actor_id = actor_id(this), file_id = file_view.get_main_file_id(), promise = std::move(promise)]( Result> &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, result.move_as_ok(), std::move(promise)); } })); return; } CHECK(input_file != nullptr); CHECK(input_file->get_id() == telegram_api::inputFile::ID); const PathView path_view(static_cast(input_file.get())->name_); auto file_name = path_view.file_name().str(); auto mime_type = MimeType::from_extension(path_view.extension()); auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( Result> &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_upload_saved_ringtone, result.move_as_ok(), std::move(promise)); } }); td_->create_handler(std::move(query_promise)) ->send(file_id, std::move(input_file), file_name, mime_type); } void NotificationSettingsManager::on_upload_ringtone_error(FileId file_id, Status status) { LOG(INFO) << "File " << file_id << " has upload error " << status; CHECK(status.is_error()); auto it = being_uploaded_ringtones_.find(file_id); if (it == being_uploaded_ringtones_.end()) { // just in case return; } auto promise = std::move(it->second.promise); being_uploaded_ringtones_.erase(it); promise.set_error(std::move(status)); } void NotificationSettingsManager::on_upload_saved_ringtone( telegram_api::object_ptr &&saved_ringtone, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_RESULT_PROMISE(promise, file_id, get_ringtone(std::move(saved_ringtone))); reload_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), file_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, nullptr, std::move(promise)); } })); } void NotificationSettingsManager::on_add_saved_ringtone( FileId file_id, telegram_api::object_ptr &&saved_ringtone, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); if (saved_ringtone != nullptr && saved_ringtone->get_id() == telegram_api::account_savedRingtoneConverted::ID) { auto ringtone = move_tl_object_as(saved_ringtone); TRY_RESULT_PROMISE_ASSIGN(promise, file_id, get_ringtone(std::move(ringtone->document_))); } else { for (auto &saved_ringtone_file_id : saved_ringtone_file_ids_) { if (file_id == saved_ringtone_file_id) { return promise.set_value(td_->audios_manager_->get_notification_sound_object(file_id)); } } if (saved_ringtone == nullptr) { return promise.set_error(Status::Error(500, "Failed to find saved notification sound")); } } reload_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), file_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, nullptr, std::move(promise)); } })); } void NotificationSettingsManager::remove_saved_ringtone(int64 ringtone_id, Promise &&promise) { if (!are_saved_ringtones_loaded_) { load_saved_ringtones(std::move(promise)); return; } for (auto &file_id : saved_ringtone_file_ids_) { auto file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.empty()); CHECK(file_view.get_type() == FileType::Ringtone); CHECK(file_view.has_remote_location()); if (file_view.remote_location().get_id() == ringtone_id) { send_save_ringtone_query( file_view.get_main_file_id(), true, PromiseCreator::lambda( [actor_id = actor_id(this), ringtone_id, promise = std::move(promise)]( Result> &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &NotificationSettingsManager::on_remove_saved_ringtone, ringtone_id, std::move(promise)); } })); return; } } promise.set_value(Unit()); } void NotificationSettingsManager::on_remove_saved_ringtone(int64 ringtone_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(are_saved_ringtones_loaded_); auto max_count = td_->option_manager_->get_option_integer("notification_sound_count_max"); if (saved_ringtone_file_ids_.size() >= static_cast(max_count)) { // reload all saved ringtones to get ringtones besides the limit return reload_saved_ringtones(PromiseCreator::lambda([promise = std::move(promise)](Result &&result) mutable { // ignore errors promise.set_value(Unit()); })); } for (auto it = saved_ringtone_file_ids_.begin(); it != saved_ringtone_file_ids_.end(); ++it) { auto file_view = td_->file_manager_->get_file_view(*it); CHECK(!file_view.empty()); CHECK(file_view.get_type() == FileType::Ringtone); CHECK(file_view.has_remote_location()); if (file_view.remote_location().get_id() == ringtone_id) { saved_ringtone_file_ids_.erase(it); saved_ringtone_hash_ = 0; on_saved_ringtones_updated(false); break; } } promise.set_value(Unit()); } Result NotificationSettingsManager::get_ringtone( telegram_api::object_ptr &&ringtone) const { int32 document_id = ringtone->get_id(); if (document_id == telegram_api::documentEmpty::ID) { return Status::Error("Receive an empty ringtone"); } CHECK(document_id == telegram_api::document::ID); auto parsed_document = td_->documents_manager_->on_get_document(move_tl_object_as(ringtone), DialogId(), nullptr, Document::Type::Audio, DocumentsManager::Subtype::Ringtone); if (parsed_document.type != Document::Type::Audio) { return Status::Error("Receive ringtone of a wrong type"); } return parsed_document.file_id; } void NotificationSettingsManager::load_saved_ringtones(Promise &&promise) { CHECK(!are_saved_ringtones_loaded_); auto saved_ringtones_string = G()->td_db()->get_binlog_pmc()->get(get_saved_ringtones_database_key()); if (saved_ringtones_string.empty()) { return reload_saved_ringtones(std::move(promise)); } RingtoneListLogEvent saved_ringtones_log_event; bool is_valid = log_event_parse(saved_ringtones_log_event, saved_ringtones_string).is_ok(); for (auto &ringtone_file_id : saved_ringtones_log_event.ringtone_file_ids_) { if (!ringtone_file_id.is_valid()) { is_valid = false; break; } } if (is_valid) { saved_ringtone_hash_ = saved_ringtones_log_event.hash_; saved_ringtone_file_ids_ = std::move(saved_ringtones_log_event.ringtone_file_ids_); are_saved_ringtones_loaded_ = true; if (!saved_ringtone_file_ids_.empty()) { on_saved_ringtones_updated(true); } // the promis must not be set synchronously send_closure_later(actor_id(this), &NotificationSettingsManager::on_load_saved_ringtones, std::move(promise)); reload_saved_ringtones(Auto()); } else { LOG(ERROR) << "Ignore invalid saved notification sounds log event"; reload_saved_ringtones(std::move(promise)); } } void NotificationSettingsManager::on_load_saved_ringtones(Promise &&promise) { promise.set_value(Unit()); } void NotificationSettingsManager::reload_saved_ringtones(Promise &&promise) { if (!is_active()) { return promise.set_error(Status::Error(400, "Don't need to reload saved notification sounds")); } reload_saved_ringtones_queries_.push_back(std::move(promise)); if (reload_saved_ringtones_queries_.size() == 1) { are_saved_ringtones_reloaded_ = true; auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result> &&result) { send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, false, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(saved_ringtone_hash_); } } void NotificationSettingsManager::repair_saved_ringtones(Promise &&promise) { if (!is_active()) { return promise.set_error(Status::Error(400, "Don't need to repair saved notification sounds")); } repair_saved_ringtones_queries_.push_back(std::move(promise)); if (repair_saved_ringtones_queries_.size() == 1u) { are_saved_ringtones_reloaded_ = true; auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result> &&result) { send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, true, std::move(result)); }); td_->create_handler(std::move(query_promise))->send(0); } } void NotificationSettingsManager::on_reload_saved_ringtones( bool is_repair, Result> &&result) { if (!is_active()) { are_saved_ringtones_loaded_ = true; set_promises(reload_saved_ringtones_queries_); set_promises(repair_saved_ringtones_queries_); return; } if (result.is_error()) { if (is_repair) { fail_promises(repair_saved_ringtones_queries_, result.move_as_error()); } else { fail_promises(reload_saved_ringtones_queries_, result.move_as_error()); set_timeout_in(Random::fast(60, 120)); } return; } if (!is_repair) { set_timeout_in(Random::fast(3600, 4800)); } auto saved_ringtones_ptr = result.move_as_ok(); auto constructor_id = saved_ringtones_ptr->get_id(); if (constructor_id == telegram_api::account_savedRingtonesNotModified::ID) { if (is_repair) { fail_promises(repair_saved_ringtones_queries_, Status::Error(500, "Failed to repair saved animations")); } else { are_saved_ringtones_loaded_ = true; set_promises(reload_saved_ringtones_queries_); } return; } CHECK(constructor_id == telegram_api::account_savedRingtones::ID); auto saved_ringtones = move_tl_object_as(saved_ringtones_ptr); auto new_hash = saved_ringtones->hash_; vector new_saved_ringtone_file_ids; for (auto &ringtone : saved_ringtones->ringtones_) { auto r_ringtone = get_ringtone(std::move(ringtone)); if (r_ringtone.is_error()) { LOG(ERROR) << r_ringtone.error().message(); new_hash = 0; continue; } new_saved_ringtone_file_ids.push_back(r_ringtone.move_as_ok()); } bool need_update = new_saved_ringtone_file_ids != saved_ringtone_file_ids_; are_saved_ringtones_loaded_ = true; if (need_update || saved_ringtone_hash_ != new_hash) { saved_ringtone_hash_ = new_hash; saved_ringtone_file_ids_ = std::move(new_saved_ringtone_file_ids); if (need_update) { on_saved_ringtones_updated(false); } } if (is_repair) { set_promises(repair_saved_ringtones_queries_); } else { set_promises(reload_saved_ringtones_queries_); } } td_api::object_ptr NotificationSettingsManager::get_update_saved_notification_sounds_object() const { auto ringtone_ids = transform(saved_ringtone_file_ids_, [file_manager = td_->file_manager_.get()](FileId file_id) { auto file_view = file_manager->get_file_view(file_id); CHECK(!file_view.empty()); CHECK(file_view.get_type() == FileType::Ringtone); CHECK(file_view.has_remote_location()); return file_view.remote_location().get_id(); }); return td_api::make_object(std::move(ringtone_ids)); } string NotificationSettingsManager::get_saved_ringtones_database_key() { return "ringtones"; } void NotificationSettingsManager::save_saved_ringtones_to_database() const { RingtoneListLogEvent ringtone_list_log_event{saved_ringtone_hash_, saved_ringtone_file_ids_}; G()->td_db()->get_binlog_pmc()->set(get_saved_ringtones_database_key(), log_event_store(ringtone_list_log_event).as_slice().str()); } void NotificationSettingsManager::on_saved_ringtones_updated(bool from_database) { CHECK(are_saved_ringtones_loaded_); vector new_sorted_saved_ringtone_file_ids = saved_ringtone_file_ids_; std::sort(new_sorted_saved_ringtone_file_ids.begin(), new_sorted_saved_ringtone_file_ids.end()); if (new_sorted_saved_ringtone_file_ids != sorted_saved_ringtone_file_ids_) { td_->file_manager_->change_files_source(get_saved_ringtones_file_source_id(), sorted_saved_ringtone_file_ids_, new_sorted_saved_ringtone_file_ids); sorted_saved_ringtone_file_ids_ = std::move(new_sorted_saved_ringtone_file_ids); } if (!from_database) { save_saved_ringtones_to_database(); } send_closure(G()->td(), &Td::send_update, get_update_saved_notification_sounds_object()); } FileSourceId NotificationSettingsManager::get_saved_ringtones_file_source_id() { if (!saved_ringtones_file_source_id_.is_valid()) { saved_ringtones_file_source_id_ = td_->file_reference_manager_->create_saved_ringtones_file_source(); } return saved_ringtones_file_source_id_; } void NotificationSettingsManager::send_get_dialog_notification_settings_query(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { if (td_->auth_manager_->is_bot()) { LOG(ERROR) << "Can't get notification settings for " << dialog_id; return promise.set_error(Status::Error(500, "Wrong getDialogNotificationSettings query")); } TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Read)); auto &promises = get_dialog_notification_settings_queries_[{dialog_id, top_thread_message_id}]; promises.push_back(std::move(promise)); if (promises.size() != 1) { // query has already been sent, just wait for the result return; } td_->create_handler()->send(dialog_id, top_thread_message_id); } const ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings( NotificationSettingsScope scope, Promise &&promise) { const ScopeNotificationSettings *notification_settings = get_scope_notification_settings(scope); CHECK(notification_settings != nullptr); if (!notification_settings->is_synchronized && !td_->auth_manager_->is_bot()) { send_get_scope_notification_settings_query(scope, std::move(promise)); return nullptr; } promise.set_value(Unit()); return notification_settings; } void NotificationSettingsManager::send_get_scope_notification_settings_query(NotificationSettingsScope scope, Promise &&promise) { if (td_->auth_manager_->is_bot()) { LOG(ERROR) << "Can't get notification settings for " << scope; return promise.set_error(Status::Error(500, "Wrong getScopeNotificationSettings query")); } td_->create_handler(std::move(promise))->send(scope); } void NotificationSettingsManager::on_get_dialog_notification_settings_query_finished(DialogId dialog_id, MessageId top_thread_message_id, Status &&status) { CHECK(!td_->auth_manager_->is_bot()); auto it = get_dialog_notification_settings_queries_.find({dialog_id, top_thread_message_id}); CHECK(it != get_dialog_notification_settings_queries_.end()); CHECK(!it->second.empty()); auto promises = std::move(it->second); get_dialog_notification_settings_queries_.erase(it); if (status.is_ok()) { set_promises(promises); } else { fail_promises(promises, std::move(status)); } } void NotificationSettingsManager::update_dialog_notify_settings(DialogId dialog_id, MessageId top_thread_message_id, const DialogNotificationSettings &new_settings, Promise &&promise) { td_->create_handler(std::move(promise)) ->send(dialog_id, top_thread_message_id, new_settings); } Status NotificationSettingsManager::set_scope_notification_settings( NotificationSettingsScope scope, td_api::object_ptr &¬ification_settings) { CHECK(!td_->auth_manager_->is_bot()); auto *current_settings = get_scope_notification_settings(scope); CHECK(current_settings != nullptr); TRY_RESULT(new_settings, ::td::get_scope_notification_settings(std::move(notification_settings))); if (is_notification_sound_default(current_settings->sound) && is_notification_sound_default(new_settings.sound)) { new_settings.sound = dup_notification_sound(current_settings->sound); } if (update_scope_notification_settings(scope, current_settings, std::move(new_settings))) { update_scope_notification_settings_on_server(scope, 0); } return Status::OK(); } class NotificationSettingsManager::UpdateScopeNotificationSettingsOnServerLogEvent { public: NotificationSettingsScope scope_; template void store(StorerT &storer) const { td::store(scope_, storer); } template void parse(ParserT &parser) { td::parse(scope_, parser); } }; uint64 NotificationSettingsManager::save_update_scope_notification_settings_on_server_log_event( NotificationSettingsScope scope) { UpdateScopeNotificationSettingsOnServerLogEvent log_event{scope}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer, get_log_event_storer(log_event)); } void NotificationSettingsManager::update_scope_notification_settings_on_server(NotificationSettingsScope scope, uint64 log_event_id) { CHECK(!td_->auth_manager_->is_bot()); if (log_event_id == 0) { log_event_id = save_update_scope_notification_settings_on_server_log_event(scope); } LOG(INFO) << "Update " << scope << " notification settings on server with log_event " << log_event_id; td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(scope, *get_scope_notification_settings(scope)); } void NotificationSettingsManager::reset_notify_settings(Promise &&promise) { td_->create_handler(std::move(promise))->send(); } Status NotificationSettingsManager::set_reaction_notification_settings( ReactionNotificationSettings &¬ification_settings) { CHECK(!td_->auth_manager_->is_bot()); notification_settings.update_default_notification_sound(reaction_notification_settings_); if (notification_settings == reaction_notification_settings_) { have_reaction_notification_settings_ = true; return Status::OK(); } VLOG(notifications) << "Update reaction notification settings from " << reaction_notification_settings_ << " to " << notification_settings; reaction_notification_settings_ = std::move(notification_settings); have_reaction_notification_settings_ = true; save_reaction_notification_settings(); send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); update_reaction_notification_settings_on_server(0); return Status::OK(); } class NotificationSettingsManager::UpdateReactionNotificationSettingsOnServerLogEvent { public: template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); END_STORE_FLAGS(); } template void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); END_PARSE_FLAGS(); } }; uint64 NotificationSettingsManager::save_update_reaction_notification_settings_on_server_log_event() { UpdateReactionNotificationSettingsOnServerLogEvent log_event; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UpdateReactionNotificationSettingsOnServer, get_log_event_storer(log_event)); } void NotificationSettingsManager::update_reaction_notification_settings_on_server(uint64 log_event_id) { CHECK(!td_->auth_manager_->is_bot()); if (log_event_id == 0) { log_event_id = save_update_reaction_notification_settings_on_server_log_event(); } LOG(INFO) << "Update reaction notification settings on server with log_event " << log_event_id; td_->create_handler(get_erase_log_event_promise(log_event_id)) ->send(reaction_notification_settings_); } void NotificationSettingsManager::get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, Promise &&promise) { td_->create_handler(std::move(promise))->send(scope, filter_scope, compare_sound); } void NotificationSettingsManager::get_story_notification_settings_exceptions( Promise> &&promise) { td_->create_handler(std::move(promise))->send(); } void NotificationSettingsManager::on_binlog_events(vector &&events) { if (G()->close_flag()) { return; } for (auto &event : events) { CHECK(event.id_ != 0); switch (event.type_) { case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer: { UpdateScopeNotificationSettingsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); update_scope_notification_settings_on_server(log_event.scope_, event.id_); break; } case LogEvent::HandlerType::UpdateReactionNotificationSettingsOnServer: { UpdateReactionNotificationSettingsOnServerLogEvent log_event; log_event_parse(log_event, event.get_data()).ensure(); update_reaction_notification_settings_on_server(event.id_); break; } default: LOG(FATAL) << "Unsupported log event type " << event.type_; } } } void NotificationSettingsManager::get_current_state(vector> &updates) const { if (td_->auth_manager_->is_bot()) { return; } for (auto scope : {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) { auto current_settings = get_scope_notification_settings(scope); CHECK(current_settings != nullptr); if (current_settings->is_synchronized) { updates.push_back(get_update_scope_notification_settings_object(scope)); } } updates.push_back(get_update_reaction_notification_settings_object()); if (are_saved_ringtones_loaded_) { updates.push_back(get_update_saved_notification_sounds_object()); } } } // namespace td