1521 lines
60 KiB
C++
1521 lines
60 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
|
|
//
|
|
// 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/ConfigShared.h"
|
|
#include "td/telegram/ContactsManager.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/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/NotificationSettings.hpp"
|
|
#include "td/telegram/NotificationSound.h"
|
|
#include "td/telegram/Td.h"
|
|
#include "td/telegram/TdDb.h"
|
|
#include "td/telegram/telegram_api.h"
|
|
#include "td/telegram/UpdatesManager.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/SliceBuilder.h"
|
|
#include "td/utils/tl_helpers.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace td {
|
|
|
|
class UploadRingtoneQuery final : public Td::ResultHandler {
|
|
FileId file_id_;
|
|
Promise<telegram_api::object_ptr<telegram_api::Document>> promise_;
|
|
|
|
public:
|
|
explicit UploadRingtoneQuery(Promise<telegram_api::object_ptr<telegram_api::Document>> &&promise)
|
|
: promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&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<telegram_api::account_uploadRingtone>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
td_->file_manager_->delete_partial_remote_location(file_id_);
|
|
|
|
auto result = result_ptr.move_as_ok();
|
|
LOG(INFO) << "Receive result for UploadRingtoneQuery: " << to_string(result);
|
|
promise_.set_value(std::move(result));
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
if (FileReferenceManager::is_file_reference_error(status)) {
|
|
LOG(ERROR) << "Receive file reference error " << status;
|
|
}
|
|
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
|
|
// TODO support FILE_PART_*_MISSING
|
|
}
|
|
|
|
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<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> promise_;
|
|
|
|
public:
|
|
explicit SaveRingtoneQuery(Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&promise)
|
|
: promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&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<telegram_api::account_saveRingtone>(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<Unit> 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<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> promise_;
|
|
|
|
public:
|
|
explicit GetSavedRingtonesQuery(Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&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<telegram_api::account_getSavedRingtones>(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_;
|
|
|
|
public:
|
|
void send(DialogId dialog_id) {
|
|
dialog_id_ = dialog_id;
|
|
auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_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<telegram_api::account_getNotifySettings>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
auto ptr = result_ptr.move_as_ok();
|
|
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_, Status::OK());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogNotifySettingsQuery");
|
|
td_->notification_settings_manager_->on_get_dialog_notification_settings_query_finished(dialog_id_,
|
|
std::move(status));
|
|
}
|
|
};
|
|
|
|
class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
|
|
public:
|
|
explicit GetNotifySettingsExceptionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(NotificationSettingsScope scope, bool filter_scope, bool compare_sound) {
|
|
int32 flags = 0;
|
|
tl_object_ptr<telegram_api::InputNotifyPeer> 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*/, std::move(input_notify_peer))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::account_getNotifyExceptions>(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<tl_object_ptr<telegram_api::User>> users;
|
|
vector<tl_object_ptr<telegram_api::Chat>> chats;
|
|
switch (updates_ptr->get_id()) {
|
|
case telegram_api::updatesCombined::ID: {
|
|
auto updates = static_cast<telegram_api::updatesCombined *>(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<telegram_api::updates *>(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_->contacts_manager_->on_get_users(std::move(users), "GetNotifySettingsExceptionsQuery");
|
|
td_->contacts_manager_->on_get_chats(std::move(chats), "GetNotifySettingsExceptionsQuery");
|
|
for (auto &dialog_id : dialog_ids) {
|
|
td_->messages_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 GetScopeNotifySettingsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
NotificationSettingsScope scope_;
|
|
|
|
public:
|
|
explicit GetScopeNotifySettingsQuery(Promise<Unit> &&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<telegram_api::account_getNotifySettings>(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 UpdateDialogNotifySettingsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
DialogId dialog_id_;
|
|
|
|
public:
|
|
explicit UpdateDialogNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(DialogId dialog_id, const DialogNotificationSettings &new_settings) {
|
|
dialog_id_ = dialog_id;
|
|
|
|
auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_id);
|
|
if (input_notify_peer == nullptr) {
|
|
return on_error(Status::Error(500, "Can't update chat notification settings"));
|
|
}
|
|
|
|
int32 flags = 0;
|
|
if (!new_settings.use_default_mute_until) {
|
|
flags |= telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK;
|
|
}
|
|
if (new_settings.sound != nullptr) {
|
|
flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK;
|
|
}
|
|
if (!new_settings.use_default_show_preview) {
|
|
flags |= telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK;
|
|
}
|
|
if (new_settings.silent_send_message) {
|
|
flags |= telegram_api::inputPeerNotifySettings::SILENT_MASK;
|
|
}
|
|
send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings(
|
|
std::move(input_notify_peer), make_tl_object<telegram_api::inputPeerNotifySettings>(
|
|
flags, new_settings.show_preview, new_settings.silent_send_message,
|
|
new_settings.mute_until, get_input_notification_sound(new_settings.sound)))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::account_updateNotifySettings>(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_->messages_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_) != nullptr) {
|
|
// trying to repair notification settings for this dialog
|
|
td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id_, Promise<>());
|
|
}
|
|
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class UpdateScopeNotifySettingsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
NotificationSettingsScope scope_;
|
|
|
|
public:
|
|
explicit UpdateScopeNotifySettingsQuery(Promise<Unit> &&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);
|
|
int32 flags = telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK |
|
|
telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK;
|
|
if (new_settings.sound != nullptr) {
|
|
flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK;
|
|
}
|
|
send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings(
|
|
std::move(input_notify_peer), make_tl_object<telegram_api::inputPeerNotifySettings>(
|
|
flags, new_settings.show_preview, false, new_settings.mute_until,
|
|
get_input_notification_sound(new_settings.sound)))));
|
|
scope_ = scope;
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::account_updateNotifySettings>(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 ResetNotifySettingsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
|
|
public:
|
|
explicit ResetNotifySettingsQuery(Promise<Unit> &&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<telegram_api::account_resetNotifySettings>(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<telegram_api::InputFile> 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<telegram_api::InputEncryptedFile> input_file) final {
|
|
UNREACHABLE();
|
|
}
|
|
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> 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<FileId> ringtone_file_ids_;
|
|
|
|
RingtoneListLogEvent() = default;
|
|
|
|
RingtoneListLogEvent(int64 hash, vector<FileId> ringtone_file_ids)
|
|
: hash_(hash), ringtone_file_ids_(std::move(ringtone_file_ids)) {
|
|
}
|
|
|
|
template <class StorerT>
|
|
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<int32>(ringtone_file_ids_.size()), storer);
|
|
for (auto ringtone_file_id : ringtone_file_ids_) {
|
|
audios_manager->store_audio(ringtone_file_id, storer);
|
|
}
|
|
}
|
|
|
|
template <class ParserT>
|
|
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<UploadRingtoneCallback>();
|
|
|
|
scope_unmute_timeout_.set_callback(on_scope_unmute_timeout_callback);
|
|
scope_unmute_timeout_.set_callback_data(static_cast<void *>(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);
|
|
|
|
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, false, false);
|
|
channels_notification_settings_.is_synchronized = false;
|
|
send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>());
|
|
}
|
|
}
|
|
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<NotificationSettingsManager *>(notification_settings_manager_ptr);
|
|
send_closure_later(notification_settings_manager->actor_id(notification_settings_manager),
|
|
&NotificationSettingsManager::on_scope_unmute,
|
|
static_cast<NotificationSettingsScope>(scope_int - 1));
|
|
}
|
|
|
|
void NotificationSettingsManager::timeout_expired() {
|
|
reload_saved_ringtones(Promise<Unit>());
|
|
}
|
|
|
|
int32 NotificationSettingsManager::get_scope_mute_until(NotificationSettingsScope scope) const {
|
|
return get_scope_notification_settings(scope)->mute_until;
|
|
}
|
|
|
|
const unique_ptr<NotificationSound> &NotificationSettingsManager::get_scope_notification_sound(
|
|
NotificationSettingsScope scope) const {
|
|
return get_scope_notification_settings(scope)->sound;
|
|
}
|
|
|
|
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<telegram_api::InputNotifyPeer> NotificationSettingsManager::get_input_notify_peer(
|
|
DialogId dialog_id) const {
|
|
if (!td_->messages_manager_->have_dialog(dialog_id)) {
|
|
return nullptr;
|
|
}
|
|
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
|
|
if (input_peer == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return make_tl_object<telegram_api::inputNotifyPeer>(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<td_api::updateScopeNotificationSettings>
|
|
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<td_api::updateScopeNotificationSettings>(
|
|
get_notification_settings_scope_object(scope), get_scope_notification_settings_object(notification_settings));
|
|
}
|
|
|
|
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 now = G()->unix_time();
|
|
if (notification_settings->mute_until > now) {
|
|
LOG(ERROR) << "Failed to unmute " << scope << " in " << now << ", will be unmuted in "
|
|
<< notification_settings->mute_until;
|
|
schedule_scope_unmute(scope, notification_settings->mute_until);
|
|
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<telegram_api::peerNotifySettings> &&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::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until) {
|
|
auto now = G()->unix_time_cached();
|
|
if (mute_until >= now && mute_until < now + 366 * 86400) {
|
|
scope_unmute_timeout_.set_timeout_in(static_cast<int64>(scope) + 1, mute_until - now + 1);
|
|
} else {
|
|
scope_unmute_timeout_.cancel_timeout(static_cast<int64>(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);
|
|
|
|
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<Unit> &&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.file_id();
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
vector<FileId> NotificationSettingsManager::get_saved_ringtones(Promise<Unit> &&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<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&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<SaveRingtoneQuery>(std::move(promise))
|
|
->send(ringtone_file_id, file_view.remote_location().as_input_document(), unsave);
|
|
}
|
|
|
|
void NotificationSettingsManager::add_saved_ringtone(td_api::object_ptr<td_api::InputFile> &&input_file,
|
|
Promise<td_api::object_ptr<td_api::notificationSound>> &&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<Unit> &&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;
|
|
}
|
|
|
|
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Ringtone, input_file, DialogId(), false, false);
|
|
if (r_file_id.is_error()) {
|
|
// TODO promise.set_error(r_file_id.move_as_error());
|
|
return promise.set_error(Status::Error(400, r_file_id.error().message()));
|
|
}
|
|
FileId file_id = r_file_id.ok();
|
|
auto file_view = td_->file_manager_->get_file_view(file_id);
|
|
CHECK(!file_view.empty());
|
|
if (file_view.size() > G()->shared_config().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 > G()->shared_config().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.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.file_id(), false,
|
|
PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), file_id = ringtone_file_id, promise = std::move(promise)](
|
|
Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&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;
|
|
}
|
|
|
|
auto download_file_id = td_->file_manager_->dup_file_id(file_id);
|
|
file_id = td_->file_manager_
|
|
->register_generate(FileType::Ringtone, FileLocationSource::FromServer, file_view.suggested_path(),
|
|
PSTRING() << "#file_id#" << download_file_id.get(), DialogId(), file_view.size())
|
|
.ok();
|
|
|
|
upload_ringtone(file_id, false, std::move(promise));
|
|
}
|
|
|
|
void NotificationSettingsManager::upload_ringtone(FileId file_id, bool is_reupload,
|
|
Promise<td_api::object_ptr<td_api::notificationSound>> &&promise,
|
|
vector<int> 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<telegram_api::InputFile> 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.file_id(), false,
|
|
PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), file_id = file_view.file_id(), promise = std::move(promise)](
|
|
Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&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<const telegram_api::inputFile *>(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<telegram_api::object_ptr<telegram_api::Document>> &&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<UploadRingtoneQuery>(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<telegram_api::Document> &&saved_ringtone,
|
|
Promise<td_api::object_ptr<td_api::notificationSound>> &&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<Unit> &&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<telegram_api::account_SavedRingtone> &&saved_ringtone,
|
|
Promise<td_api::object_ptr<td_api::notificationSound>> &&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<telegram_api::account_savedRingtoneConverted>(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<Unit> &&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<Unit> &&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.file_id(), true,
|
|
PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), ringtone_id, promise = std::move(promise)](
|
|
Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&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<Unit> &&promise) {
|
|
TRY_STATUS_PROMISE(promise, G()->close_status());
|
|
|
|
CHECK(are_saved_ringtones_loaded_);
|
|
|
|
auto max_count = G()->shared_config().get_option_integer("notification_sound_count_max");
|
|
if (saved_ringtone_file_ids_.size() >= static_cast<uint64>(max_count)) {
|
|
// reload all saved ringtones to get ringtones besides the limit
|
|
return reload_saved_ringtones(PromiseCreator::lambda([promise = std::move(promise)](Result<Unit> &&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<FileId> NotificationSettingsManager::get_ringtone(
|
|
telegram_api::object_ptr<telegram_api::Document> &&ringtone) const {
|
|
int32 document_id = ringtone->get_id();
|
|
if (document_id == telegram_api::documentEmpty::ID) {
|
|
return Status::Error("Received an empty ringtone");
|
|
}
|
|
CHECK(document_id == telegram_api::document::ID);
|
|
|
|
auto parsed_document =
|
|
td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(ringtone), DialogId(), nullptr,
|
|
Document::Type::Audio, false, false, true);
|
|
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<Unit> &&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<Unit> &&promise) {
|
|
promise.set_value(Unit());
|
|
}
|
|
|
|
void NotificationSettingsManager::reload_saved_ringtones(Promise<Unit> &&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<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result) {
|
|
send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, false, std::move(result));
|
|
});
|
|
td_->create_handler<GetSavedRingtonesQuery>(std::move(query_promise))->send(saved_ringtone_hash_);
|
|
}
|
|
}
|
|
|
|
void NotificationSettingsManager::repair_saved_ringtones(Promise<Unit> &&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<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result) {
|
|
send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, true, std::move(result));
|
|
});
|
|
td_->create_handler<GetSavedRingtonesQuery>(std::move(query_promise))->send(0);
|
|
}
|
|
}
|
|
|
|
void NotificationSettingsManager::on_reload_saved_ringtones(
|
|
bool is_repair, Result<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&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<telegram_api::account_savedRingtones>(saved_ringtones_ptr);
|
|
|
|
auto new_hash = saved_ringtones->hash_;
|
|
vector<FileId> 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<td_api::updateSavedNotificationSounds>
|
|
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<td_api::updateSavedNotificationSounds>(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<FileId> 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,
|
|
Promise<Unit> &&promise) {
|
|
if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
|
|
LOG(WARNING) << "Can't get notification settings for " << dialog_id;
|
|
return promise.set_error(Status::Error(500, "Wrong getDialogNotificationSettings query"));
|
|
}
|
|
if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
|
|
LOG(WARNING) << "Have no access to " << dialog_id << " to get notification settings";
|
|
return promise.set_error(Status::Error(400, "Can't access the chat"));
|
|
}
|
|
|
|
auto &promises = get_dialog_notification_settings_queries_[dialog_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<GetDialogNotifySettingsQuery>()->send(dialog_id);
|
|
}
|
|
|
|
const ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings(
|
|
NotificationSettingsScope scope, Promise<Unit> &&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<Unit> &&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<GetScopeNotifySettingsQuery>(std::move(promise))->send(scope);
|
|
}
|
|
|
|
void NotificationSettingsManager::on_get_dialog_notification_settings_query_finished(DialogId dialog_id,
|
|
Status &&status) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto it = get_dialog_notification_settings_queries_.find(dialog_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,
|
|
const DialogNotificationSettings &new_settings,
|
|
Promise<Unit> &&promise) {
|
|
td_->create_handler<UpdateDialogNotifySettingsQuery>(std::move(promise))->send(dialog_id, new_settings);
|
|
}
|
|
|
|
Status NotificationSettingsManager::set_scope_notification_settings(
|
|
NotificationSettingsScope scope, td_api::object_ptr<td_api::scopeNotificationSettings> &¬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 <class StorerT>
|
|
void store(StorerT &storer) const {
|
|
td::store(scope_, storer);
|
|
}
|
|
|
|
template <class ParserT>
|
|
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<UpdateScopeNotifySettingsQuery>(get_erase_log_event_promise(log_event_id))
|
|
->send(scope, *get_scope_notification_settings(scope));
|
|
}
|
|
|
|
void NotificationSettingsManager::reset_notify_settings(Promise<Unit> &&promise) {
|
|
td_->create_handler<ResetNotifySettingsQuery>(std::move(promise))->send();
|
|
}
|
|
|
|
void NotificationSettingsManager::get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope,
|
|
bool compare_sound, Promise<Unit> &&promise) {
|
|
td_->create_handler<GetNotifySettingsExceptionsQuery>(std::move(promise))->send(scope, filter_scope, compare_sound);
|
|
}
|
|
|
|
void NotificationSettingsManager::after_get_difference() {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
return;
|
|
}
|
|
|
|
if (!users_notification_settings_.is_synchronized) {
|
|
send_get_scope_notification_settings_query(NotificationSettingsScope::Private, Promise<>());
|
|
}
|
|
if (!chats_notification_settings_.is_synchronized) {
|
|
send_get_scope_notification_settings_query(NotificationSettingsScope::Group, Promise<>());
|
|
}
|
|
if (!channels_notification_settings_.is_synchronized) {
|
|
send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>());
|
|
}
|
|
|
|
if (td_->is_online() && !are_saved_ringtones_reloaded_) {
|
|
reload_saved_ringtones(Auto());
|
|
}
|
|
}
|
|
|
|
void NotificationSettingsManager::on_binlog_events(vector<BinlogEvent> &&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.data_).ensure();
|
|
|
|
update_scope_notification_settings_on_server(log_event.scope_, event.id_);
|
|
break;
|
|
}
|
|
default:
|
|
LOG(FATAL) << "Unsupported log event type " << event.type_;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotificationSettingsManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &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));
|
|
}
|
|
}
|
|
|
|
if (are_saved_ringtones_loaded_) {
|
|
updates.push_back(get_update_saved_notification_sounds_object());
|
|
}
|
|
}
|
|
|
|
} // namespace td
|