1482 lines
58 KiB
C++
1482 lines
58 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
|
//
|
|
// 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/DialogFilterManager.h"
|
|
|
|
#include "td/telegram/AccessRights.h"
|
|
#include "td/telegram/AuthManager.h"
|
|
#include "td/telegram/ContactsManager.h"
|
|
#include "td/telegram/DialogFilter.h"
|
|
#include "td/telegram/DialogFilter.hpp"
|
|
#include "td/telegram/DialogFilterInviteLink.h"
|
|
#include "td/telegram/Global.h"
|
|
#include "td/telegram/logevent/LogEvent.h"
|
|
#include "td/telegram/MessagesManager.h"
|
|
#include "td/telegram/OptionManager.h"
|
|
#include "td/telegram/Td.h"
|
|
#include "td/telegram/TdDb.h"
|
|
#include "td/telegram/Version.h"
|
|
|
|
#include "td/actor/MultiPromise.h"
|
|
|
|
#include "td/utils/algorithm.h"
|
|
#include "td/utils/buffer.h"
|
|
#include "td/utils/FlatHashMap.h"
|
|
#include "td/utils/logging.h"
|
|
#include "td/utils/Random.h"
|
|
#include "td/utils/tl_helpers.h"
|
|
|
|
#include <unordered_set>
|
|
|
|
namespace td {
|
|
|
|
class GetDialogFiltersQuery final : public Td::ResultHandler {
|
|
Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> promise_;
|
|
|
|
public:
|
|
explicit GetDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> &&promise)
|
|
: promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send() {
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_getDialogFilters()));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_getDialogFilters>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
promise_.set_value(result_ptr.move_as_ok());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class UpdateDialogFilterQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
|
|
public:
|
|
explicit UpdateDialogFilterQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(DialogFilterId dialog_filter_id, telegram_api::object_ptr<telegram_api::DialogFilter> filter) {
|
|
int32 flags = 0;
|
|
if (filter != nullptr) {
|
|
flags |= telegram_api::messages_updateDialogFilter::FILTER_MASK;
|
|
}
|
|
send_query(G()->net_query_creator().create(
|
|
telegram_api::messages_updateDialogFilter(flags, dialog_filter_id.get(), std::move(filter))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_updateDialogFilter>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
LOG(INFO) << "Receive result for UpdateDialogFilterQuery: " << result_ptr.ok();
|
|
promise_.set_value(Unit());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
LOG(ERROR) << "Receive error for UpdateDialogFilterQuery: " << status;
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class UpdateDialogFiltersOrderQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
|
|
public:
|
|
explicit UpdateDialogFiltersOrderQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(const vector<DialogFilterId> &dialog_filter_ids, int32 main_dialog_list_position) {
|
|
auto filter_ids = transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); });
|
|
CHECK(0 <= main_dialog_list_position);
|
|
CHECK(main_dialog_list_position <= static_cast<int32>(filter_ids.size()));
|
|
filter_ids.insert(filter_ids.begin() + main_dialog_list_position, 0);
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder(std::move(filter_ids))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_updateDialogFiltersOrder>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
LOG(INFO) << "Receive result for UpdateDialogFiltersOrderQuery: " << result_ptr.ok();
|
|
promise_.set_value(Unit());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class ExportChatlistInviteQuery final : public Td::ResultHandler {
|
|
Promise<td_api::object_ptr<td_api::chatFilterInviteLink>> promise_;
|
|
|
|
public:
|
|
explicit ExportChatlistInviteQuery(Promise<td_api::object_ptr<td_api::chatFilterInviteLink>> &&promise)
|
|
: promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(DialogFilterId dialog_filter_id, const string &title,
|
|
vector<tl_object_ptr<telegram_api::InputPeer>> &&input_peers) {
|
|
send_query(G()->net_query_creator().create(telegram_api::chatlists_exportChatlistInvite(
|
|
dialog_filter_id.get_input_chatlist(), title, std::move(input_peers))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::chatlists_exportChatlistInvite>(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 ExportChatlistInviteQuery: " << to_string(ptr);
|
|
td_->dialog_filter_manager_->on_get_dialog_filter(std::move(ptr->filter_));
|
|
promise_.set_value(DialogFilterInviteLink(td_, std::move(ptr->invite_)).get_chat_filter_invite_link_object());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class GetDialogsQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
bool is_single_ = false;
|
|
|
|
public:
|
|
explicit GetDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(vector<InputDialogId> input_dialog_ids) {
|
|
CHECK(!input_dialog_ids.empty());
|
|
CHECK(input_dialog_ids.size() <= 100);
|
|
is_single_ = input_dialog_ids.size() == 1;
|
|
auto input_dialog_peers = InputDialogId::get_input_dialog_peers(input_dialog_ids);
|
|
CHECK(input_dialog_peers.size() == input_dialog_ids.size());
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_getPeerDialogs(std::move(input_dialog_peers))));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(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 GetDialogsQuery: " << to_string(result);
|
|
|
|
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery");
|
|
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery");
|
|
td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_),
|
|
std::move(promise_));
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
if (is_single_ && status.code() == 400) {
|
|
return promise_.set_value(Unit());
|
|
}
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class GetSuggestedDialogFiltersQuery final : public Td::ResultHandler {
|
|
Promise<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> promise_;
|
|
|
|
public:
|
|
explicit GetSuggestedDialogFiltersQuery(
|
|
Promise<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> &&promise)
|
|
: promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send() {
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_getSuggestedDialogFilters()));
|
|
}
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_getSuggestedDialogFilters>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(result_ptr.move_as_error());
|
|
}
|
|
|
|
promise_.set_value(result_ptr.move_as_ok());
|
|
}
|
|
|
|
void on_error(Status status) final {
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
DialogFilterManager::DialogFilterManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
|
|
}
|
|
|
|
void DialogFilterManager::hangup() {
|
|
fail_promises(dialog_filter_reload_queries_, Global::request_aborted_error());
|
|
stop();
|
|
}
|
|
|
|
void DialogFilterManager::tear_down() {
|
|
parent_.reset();
|
|
}
|
|
|
|
class DialogFilterManager::DialogFiltersLogEvent {
|
|
public:
|
|
int32 server_main_dialog_list_position = 0;
|
|
int32 main_dialog_list_position = 0;
|
|
int32 updated_date = 0;
|
|
const vector<unique_ptr<DialogFilter>> *server_dialog_filters_in;
|
|
const vector<unique_ptr<DialogFilter>> *dialog_filters_in;
|
|
vector<unique_ptr<DialogFilter>> server_dialog_filters_out;
|
|
vector<unique_ptr<DialogFilter>> dialog_filters_out;
|
|
|
|
template <class StorerT>
|
|
void store(StorerT &storer) const {
|
|
bool has_server_dialog_filters = !server_dialog_filters_in->empty();
|
|
bool has_dialog_filters = !dialog_filters_in->empty();
|
|
bool has_server_main_dialog_list_position = server_main_dialog_list_position != 0;
|
|
bool has_main_dialog_list_position = main_dialog_list_position != 0;
|
|
BEGIN_STORE_FLAGS();
|
|
STORE_FLAG(has_server_dialog_filters);
|
|
STORE_FLAG(has_dialog_filters);
|
|
STORE_FLAG(has_server_main_dialog_list_position);
|
|
STORE_FLAG(has_main_dialog_list_position);
|
|
END_STORE_FLAGS();
|
|
td::store(updated_date, storer);
|
|
if (has_server_dialog_filters) {
|
|
td::store(*server_dialog_filters_in, storer);
|
|
}
|
|
if (has_dialog_filters) {
|
|
td::store(*dialog_filters_in, storer);
|
|
}
|
|
if (has_server_main_dialog_list_position) {
|
|
td::store(server_main_dialog_list_position, storer);
|
|
}
|
|
if (has_main_dialog_list_position) {
|
|
td::store(main_dialog_list_position, storer);
|
|
}
|
|
}
|
|
|
|
template <class ParserT>
|
|
void parse(ParserT &parser) {
|
|
bool has_server_dialog_filters = true;
|
|
bool has_dialog_filters = true;
|
|
bool has_server_main_dialog_list_position = false;
|
|
bool has_main_dialog_list_position = false;
|
|
if (parser.version() >= static_cast<int32>(Version::AddMainDialogListPosition)) {
|
|
BEGIN_PARSE_FLAGS();
|
|
PARSE_FLAG(has_server_dialog_filters);
|
|
PARSE_FLAG(has_dialog_filters);
|
|
PARSE_FLAG(has_server_main_dialog_list_position);
|
|
PARSE_FLAG(has_main_dialog_list_position);
|
|
END_PARSE_FLAGS();
|
|
}
|
|
td::parse(updated_date, parser);
|
|
if (has_server_dialog_filters) {
|
|
td::parse(server_dialog_filters_out, parser);
|
|
}
|
|
if (has_dialog_filters) {
|
|
td::parse(dialog_filters_out, parser);
|
|
}
|
|
if (has_server_main_dialog_list_position) {
|
|
td::parse(server_main_dialog_list_position, parser);
|
|
}
|
|
if (has_main_dialog_list_position) {
|
|
td::parse(main_dialog_list_position, parser);
|
|
}
|
|
}
|
|
};
|
|
|
|
void DialogFilterManager::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 (is_authorized && td_->auth_manager_->is_bot()) {
|
|
disable_get_dialog_filter_ = true;
|
|
}
|
|
|
|
if (was_authorized_user) {
|
|
auto dialog_filters = G()->td_db()->get_binlog_pmc()->get("dialog_filters");
|
|
if (!dialog_filters.empty()) {
|
|
DialogFiltersLogEvent log_event;
|
|
if (log_event_parse(log_event, dialog_filters).is_ok()) {
|
|
server_main_dialog_list_position_ = log_event.server_main_dialog_list_position;
|
|
main_dialog_list_position_ = log_event.main_dialog_list_position;
|
|
if (!td_->option_manager_->get_option_boolean("is_premium") &&
|
|
(server_main_dialog_list_position_ != 0 || main_dialog_list_position_ != 0)) {
|
|
LOG(INFO) << "Ignore main chat list position " << server_main_dialog_list_position_ << '/'
|
|
<< main_dialog_list_position_;
|
|
server_main_dialog_list_position_ = 0;
|
|
main_dialog_list_position_ = 0;
|
|
}
|
|
|
|
dialog_filters_updated_date_ = td_->ignore_background_updates() ? 0 : log_event.updated_date;
|
|
std::unordered_set<DialogFilterId, DialogFilterIdHash> server_dialog_filter_ids;
|
|
for (auto &dialog_filter : log_event.server_dialog_filters_out) {
|
|
if (dialog_filter->get_dialog_filter_id().is_valid() &&
|
|
server_dialog_filter_ids.insert(dialog_filter->get_dialog_filter_id()).second) {
|
|
server_dialog_filters_.push_back(std::move(dialog_filter));
|
|
}
|
|
}
|
|
for (auto &dialog_filter : log_event.dialog_filters_out) {
|
|
add_dialog_filter(std::move(dialog_filter), false, "binlog");
|
|
}
|
|
LOG(INFO) << "Loaded server chat filters "
|
|
<< DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
|
|
<< " and local chat filters "
|
|
<< DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
|
|
} else {
|
|
LOG(ERROR) << "Failed to parse chat filters from binlog";
|
|
}
|
|
}
|
|
send_update_chat_filters(); // always send updateChatFilters
|
|
|
|
if (is_authorized) {
|
|
if (need_synchronize_dialog_filters()) {
|
|
reload_dialog_filters();
|
|
} else {
|
|
auto cache_time = get_dialog_filters_cache_time();
|
|
schedule_dialog_filters_reload(cache_time - max(0, G()->unix_time() - dialog_filters_updated_date_));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DialogFilterManager::on_authorization_success() {
|
|
CHECK(td_->auth_manager_->is_authorized());
|
|
if (td_->auth_manager_->is_bot()) {
|
|
disable_get_dialog_filter_ = true;
|
|
return;
|
|
}
|
|
|
|
reload_dialog_filters();
|
|
}
|
|
|
|
void DialogFilterManager::on_update_dialog_filters() {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
// just in case
|
|
return;
|
|
}
|
|
|
|
schedule_dialog_filters_reload(0.0);
|
|
}
|
|
|
|
void DialogFilterManager::schedule_reload_dialog_filters(Promise<Unit> &&promise) {
|
|
schedule_dialog_filters_reload(0.0);
|
|
dialog_filter_reload_queries_.push_back(std::move(promise));
|
|
}
|
|
|
|
bool DialogFilterManager::have_dialog_filters() const {
|
|
return !dialog_filters_.empty();
|
|
}
|
|
|
|
vector<FolderId> DialogFilterManager::get_dialog_filter_folder_ids(DialogFilterId dialog_filter_id) const {
|
|
const auto *dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(dialog_filter != nullptr);
|
|
return dialog_filter->get_folder_ids();
|
|
}
|
|
|
|
vector<DialogFilterId> DialogFilterManager::get_dialog_filters_to_add_dialog(DialogId dialog_id) const {
|
|
vector<DialogFilterId> result;
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
if (dialog_filter->can_include_dialog(dialog_id)) {
|
|
result.push_back(dialog_filter->get_dialog_filter_id());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DialogFilterManager::need_dialog_in_filter(DialogFilterId dialog_filter_id,
|
|
const DialogFilterDialogInfo &dialog_info) const {
|
|
const auto *dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(dialog_filter != nullptr);
|
|
return dialog_filter->need_dialog(td_, dialog_info);
|
|
}
|
|
|
|
bool DialogFilterManager::is_dialog_pinned(DialogFilterId dialog_filter_id, DialogId dialog_id) const {
|
|
const auto *dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
return dialog_filter != nullptr && dialog_filter->is_dialog_pinned(dialog_id);
|
|
}
|
|
|
|
const vector<InputDialogId> &DialogFilterManager::get_pinned_input_dialog_ids(DialogFilterId dialog_filter_id) const {
|
|
auto *dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(dialog_filter != nullptr);
|
|
return dialog_filter->get_pinned_input_dialog_ids();
|
|
}
|
|
|
|
vector<DialogId> DialogFilterManager::get_pinned_dialog_ids(DialogFilterId dialog_filter_id) const {
|
|
auto *dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return {};
|
|
}
|
|
return InputDialogId::get_dialog_ids(dialog_filter->get_pinned_input_dialog_ids());
|
|
}
|
|
|
|
Status DialogFilterManager::set_dialog_is_pinned(DialogFilterId dialog_filter_id, InputDialogId input_dialog_id,
|
|
bool is_pinned) {
|
|
CHECK(is_update_chat_filters_sent_);
|
|
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(old_dialog_filter != nullptr);
|
|
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
|
|
new_dialog_filter->set_dialog_is_pinned(input_dialog_id, is_pinned);
|
|
|
|
TRY_STATUS(new_dialog_filter->check_limits());
|
|
new_dialog_filter->sort_input_dialog_ids(td_, "set_dialog_is_pinned");
|
|
|
|
do_edit_dialog_filter(std::move(new_dialog_filter),
|
|
input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat, "set_dialog_is_pinned");
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DialogFilterManager::set_pinned_dialog_ids(DialogFilterId dialog_filter_id,
|
|
vector<InputDialogId> input_dialog_ids, bool need_synchronize) {
|
|
CHECK(is_update_chat_filters_sent_);
|
|
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(old_dialog_filter != nullptr);
|
|
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
|
|
new_dialog_filter->set_pinned_dialog_ids(std::move(input_dialog_ids));
|
|
TRY_STATUS(new_dialog_filter->check_limits());
|
|
new_dialog_filter->sort_input_dialog_ids(td_, "set_pinned_dialog_ids");
|
|
|
|
do_edit_dialog_filter(std::move(new_dialog_filter), need_synchronize, "set_pinned_dialog_ids");
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DialogFilterManager::add_dialog(DialogFilterId dialog_filter_id, InputDialogId input_dialog_id) {
|
|
CHECK(is_update_chat_filters_sent_);
|
|
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
CHECK(old_dialog_filter != nullptr);
|
|
if (old_dialog_filter->is_dialog_included(input_dialog_id.get_dialog_id())) {
|
|
return Status::OK();
|
|
}
|
|
|
|
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
|
|
new_dialog_filter->include_dialog(input_dialog_id);
|
|
TRY_STATUS(new_dialog_filter->check_limits());
|
|
new_dialog_filter->sort_input_dialog_ids(td_, "add_dialog");
|
|
|
|
do_edit_dialog_filter(std::move(new_dialog_filter),
|
|
input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat, "add_dialog");
|
|
return Status::OK();
|
|
}
|
|
|
|
bool DialogFilterManager::is_recommended_dialog_filter(const DialogFilter *dialog_filter) {
|
|
for (const auto &recommended_dialog_filter : recommended_dialog_filters_) {
|
|
if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
td_api::object_ptr<td_api::chatFilter> DialogFilterManager::get_chat_filter_object(DialogFilterId dialog_filter_id) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return get_chat_filter_object(dialog_filter);
|
|
}
|
|
|
|
td_api::object_ptr<td_api::chatFilter> DialogFilterManager::get_chat_filter_object(const DialogFilter *dialog_filter) {
|
|
DialogFilterId dialog_filter_id = dialog_filter->get_dialog_filter_id();
|
|
|
|
vector<DialogId> left_dialog_ids;
|
|
vector<DialogId> unknown_dialog_ids;
|
|
dialog_filter->for_each_dialog([&](const InputDialogId &input_dialog_id) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
if (td_->messages_manager_->is_dialog_in_dialog_list(dialog_id)) {
|
|
return;
|
|
}
|
|
if (td_->messages_manager_->have_dialog(dialog_id)) {
|
|
LOG(INFO) << "Skip nonjoined " << dialog_id << " from " << dialog_filter_id;
|
|
unknown_dialog_ids.push_back(dialog_id);
|
|
left_dialog_ids.push_back(dialog_id);
|
|
} else {
|
|
LOG(ERROR) << "Can't find " << dialog_id << " from " << dialog_filter_id;
|
|
unknown_dialog_ids.push_back(dialog_id);
|
|
}
|
|
});
|
|
|
|
auto result = dialog_filter->get_chat_filter_object(unknown_dialog_ids);
|
|
|
|
if (dialog_filter_id.is_valid()) {
|
|
delete_dialogs_from_filter(dialog_filter, std::move(left_dialog_ids), "get_chat_filter_object");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void DialogFilterManager::get_recommended_dialog_filters(
|
|
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto query_promise = PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), promise = std::move(promise)](
|
|
Result<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> result) mutable {
|
|
send_closure(actor_id, &DialogFilterManager::on_get_recommended_dialog_filters, std::move(result),
|
|
std::move(promise));
|
|
});
|
|
td_->create_handler<GetSuggestedDialogFiltersQuery>(std::move(query_promise))->send();
|
|
}
|
|
|
|
void DialogFilterManager::on_get_recommended_dialog_filters(
|
|
Result<vector<telegram_api::object_ptr<telegram_api::dialogFilterSuggested>>> result,
|
|
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
|
|
if (result.is_error()) {
|
|
return promise.set_error(result.move_as_error());
|
|
}
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto suggested_filters = result.move_as_ok();
|
|
|
|
MultiPromiseActorSafe mpas{"LoadRecommendedFiltersMultiPromiseActor"};
|
|
mpas.add_promise(Promise<Unit>());
|
|
auto lock = mpas.get_promise();
|
|
|
|
vector<RecommendedDialogFilter> filters;
|
|
for (auto &suggested_filter : suggested_filters) {
|
|
RecommendedDialogFilter recommended_dialog_filter;
|
|
recommended_dialog_filter.dialog_filter =
|
|
DialogFilter::get_dialog_filter(std::move(suggested_filter->filter_), false);
|
|
if (recommended_dialog_filter.dialog_filter == nullptr) {
|
|
continue;
|
|
}
|
|
load_dialog_filter(recommended_dialog_filter.dialog_filter.get(), false, mpas.get_promise());
|
|
|
|
recommended_dialog_filter.description = std::move(suggested_filter->description_);
|
|
filters.push_back(std::move(recommended_dialog_filter));
|
|
}
|
|
|
|
mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), filters = std::move(filters),
|
|
promise = std::move(promise)](Result<Unit> &&result) mutable {
|
|
send_closure(actor_id, &DialogFilterManager::on_load_recommended_dialog_filters, std::move(result),
|
|
std::move(filters), std::move(promise));
|
|
}));
|
|
lock.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::on_load_recommended_dialog_filters(
|
|
Result<Unit> &&result, vector<RecommendedDialogFilter> &&filters,
|
|
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
|
|
TRY_STATUS_PROMISE(promise, G()->close_status());
|
|
if (result.is_error()) {
|
|
return promise.set_error(result.move_as_error());
|
|
}
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
|
|
auto chat_filters = transform(filters, [this](const RecommendedDialogFilter &recommended_dialog_filter) {
|
|
return td_api::make_object<td_api::recommendedChatFilter>(
|
|
get_chat_filter_object(recommended_dialog_filter.dialog_filter.get()), recommended_dialog_filter.description);
|
|
});
|
|
recommended_dialog_filters_ = std::move(filters);
|
|
promise.set_value(td_api::make_object<td_api::recommendedChatFilters>(std::move(chat_filters)));
|
|
}
|
|
|
|
void DialogFilterManager::load_dialog_filter(DialogFilterId dialog_filter_id, bool force, Promise<Unit> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (!dialog_filter_id.is_valid()) {
|
|
return promise.set_error(Status::Error(400, "Invalid chat filter identifier specified"));
|
|
}
|
|
|
|
auto dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return promise.set_value(Unit());
|
|
}
|
|
|
|
load_dialog_filter(dialog_filter, force, std::move(promise));
|
|
}
|
|
|
|
void DialogFilterManager::load_dialog_filter(const DialogFilter *dialog_filter, bool force, Promise<Unit> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
vector<InputDialogId> needed_dialog_ids;
|
|
dialog_filter->for_each_dialog([&](const InputDialogId &input_dialog_id) {
|
|
if (!td_->messages_manager_->have_dialog(input_dialog_id.get_dialog_id())) {
|
|
needed_dialog_ids.push_back(input_dialog_id);
|
|
}
|
|
});
|
|
|
|
vector<InputDialogId> input_dialog_ids;
|
|
for (const auto &input_dialog_id : needed_dialog_ids) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
// TODO load dialogs asynchronously
|
|
if (!td_->messages_manager_->have_dialog_force(dialog_id, "load_dialog_filter")) {
|
|
if (dialog_id.get_type() == DialogType::SecretChat) {
|
|
if (td_->messages_manager_->have_dialog_info_force(dialog_id)) {
|
|
td_->messages_manager_->force_create_dialog(dialog_id, "load_dialog_filter");
|
|
}
|
|
} else {
|
|
input_dialog_ids.push_back(input_dialog_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!input_dialog_ids.empty() && !force) {
|
|
return load_dialog_filter_dialogs(dialog_filter->get_dialog_filter_id(), std::move(input_dialog_ids),
|
|
std::move(promise));
|
|
}
|
|
|
|
promise.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::load_dialog_filter_dialogs(DialogFilterId dialog_filter_id,
|
|
vector<InputDialogId> &&input_dialog_ids,
|
|
Promise<Unit> &&promise) {
|
|
const size_t MAX_SLICE_SIZE = 100; // server side limit
|
|
MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"};
|
|
mpas.add_promise(std::move(promise));
|
|
auto lock = mpas.get_promise();
|
|
|
|
for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) {
|
|
auto end_i = i + MAX_SLICE_SIZE;
|
|
auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end();
|
|
vector<InputDialogId> slice_input_dialog_ids = {input_dialog_ids.begin() + i, end};
|
|
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id,
|
|
dialog_ids = InputDialogId::get_dialog_ids(slice_input_dialog_ids),
|
|
promise = mpas.get_promise()](Result<Unit> &&result) mutable {
|
|
if (result.is_error()) {
|
|
return promise.set_error(result.move_as_error());
|
|
}
|
|
send_closure(actor_id, &DialogFilterManager::on_load_dialog_filter_dialogs, dialog_filter_id,
|
|
std::move(dialog_ids), std::move(promise));
|
|
});
|
|
td_->create_handler<GetDialogsQuery>(std::move(query_promise))->send(std::move(slice_input_dialog_ids));
|
|
}
|
|
|
|
lock.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<DialogId> &&dialog_ids,
|
|
Promise<Unit> &&promise) {
|
|
TRY_STATUS_PROMISE(promise, G()->close_status());
|
|
|
|
td::remove_if(dialog_ids, [messages_manager = td_->messages_manager_.get()](DialogId dialog_id) {
|
|
return messages_manager->have_dialog_force(dialog_id, "on_load_dialog_filter_dialogs");
|
|
});
|
|
if (dialog_ids.empty()) {
|
|
LOG(INFO) << "All chats from " << dialog_filter_id << " were loaded";
|
|
return promise.set_value(Unit());
|
|
}
|
|
|
|
LOG(INFO) << "Failed to load chats " << dialog_ids << " from " << dialog_filter_id;
|
|
|
|
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (old_dialog_filter == nullptr) {
|
|
return promise.set_value(Unit());
|
|
}
|
|
CHECK(is_update_chat_filters_sent_);
|
|
|
|
delete_dialogs_from_filter(old_dialog_filter, std::move(dialog_ids), "on_load_dialog_filter_dialogs");
|
|
|
|
promise.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::delete_dialogs_from_filter(const DialogFilter *dialog_filter, vector<DialogId> &&dialog_ids,
|
|
const char *source) {
|
|
if (dialog_ids.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto new_dialog_filter = td::make_unique<DialogFilter>(*dialog_filter);
|
|
for (auto dialog_id : dialog_ids) {
|
|
new_dialog_filter->remove_dialog_id(dialog_id);
|
|
}
|
|
if (new_dialog_filter->is_empty(false)) {
|
|
delete_dialog_filter(dialog_filter->get_dialog_filter_id(), Promise<Unit>());
|
|
return;
|
|
}
|
|
CHECK(new_dialog_filter->check_limits().is_ok());
|
|
|
|
if (*new_dialog_filter != *dialog_filter) {
|
|
LOG(INFO) << "Update " << *dialog_filter << " to " << *new_dialog_filter;
|
|
do_edit_dialog_filter(std::move(new_dialog_filter), true, "delete_dialogs_from_filter");
|
|
}
|
|
}
|
|
|
|
const DialogFilter *DialogFilterManager::get_server_dialog_filter(DialogFilterId dialog_filter_id) const {
|
|
CHECK(!disable_get_dialog_filter_);
|
|
for (const auto &dialog_filter : server_dialog_filters_) {
|
|
if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) {
|
|
return dialog_filter.get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
DialogFilter *DialogFilterManager::get_dialog_filter(DialogFilterId dialog_filter_id) {
|
|
CHECK(!disable_get_dialog_filter_);
|
|
for (auto &dialog_filter : dialog_filters_) {
|
|
if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) {
|
|
return dialog_filter.get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const DialogFilter *DialogFilterManager::get_dialog_filter(DialogFilterId dialog_filter_id) const {
|
|
CHECK(!disable_get_dialog_filter_);
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
if (dialog_filter->get_dialog_filter_id() == dialog_filter_id) {
|
|
return dialog_filter.get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int32 DialogFilterManager::get_server_main_dialog_list_position() const {
|
|
int32 current_position = 0;
|
|
int32 current_server_position = 0;
|
|
if (current_position == main_dialog_list_position_) {
|
|
return current_server_position;
|
|
}
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
current_position++;
|
|
if (!dialog_filter->is_empty(true)) {
|
|
current_server_position++;
|
|
}
|
|
if (current_position == main_dialog_list_position_) {
|
|
return current_server_position;
|
|
}
|
|
}
|
|
LOG(WARNING) << "Failed to find server position for " << main_dialog_list_position_ << " in chat filters";
|
|
return current_server_position;
|
|
}
|
|
|
|
double DialogFilterManager::get_dialog_filters_cache_time() {
|
|
return DIALOG_FILTERS_CACHE_TIME * 0.0001 * Random::fast(9000, 11000);
|
|
}
|
|
|
|
void DialogFilterManager::schedule_dialog_filters_reload(double timeout) {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
// just in case
|
|
return;
|
|
}
|
|
if (timeout <= 0) {
|
|
timeout = 0.0;
|
|
if (dialog_filters_updated_date_ != 0) {
|
|
dialog_filters_updated_date_ = 0;
|
|
save_dialog_filters();
|
|
}
|
|
}
|
|
LOG(INFO) << "Schedule reload of chat filters in " << timeout;
|
|
reload_dialog_filters_timeout_.set_callback(std::move(DialogFilterManager::on_reload_dialog_filters_timeout));
|
|
reload_dialog_filters_timeout_.set_callback_data(static_cast<void *>(this));
|
|
reload_dialog_filters_timeout_.set_timeout_in(timeout);
|
|
}
|
|
|
|
void DialogFilterManager::on_reload_dialog_filters_timeout(void *messages_manager_ptr) {
|
|
if (G()->close_flag()) {
|
|
return;
|
|
}
|
|
auto dialog_filter_manager = static_cast<DialogFilterManager *>(messages_manager_ptr);
|
|
send_closure_later(dialog_filter_manager->actor_id(dialog_filter_manager),
|
|
&DialogFilterManager::reload_dialog_filters);
|
|
}
|
|
|
|
void DialogFilterManager::reload_dialog_filters() {
|
|
if (G()->close_flag()) {
|
|
return;
|
|
}
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
|
|
need_dialog_filters_reload_ = true;
|
|
return;
|
|
}
|
|
LOG(INFO) << "Reload chat filters from server";
|
|
are_dialog_filters_being_reloaded_ = true;
|
|
need_dialog_filters_reload_ = false;
|
|
auto promise = PromiseCreator::lambda(
|
|
[actor_id = actor_id(this)](Result<vector<telegram_api::object_ptr<telegram_api::DialogFilter>>> r_filters) {
|
|
send_closure(actor_id, &DialogFilterManager::on_get_dialog_filters, std::move(r_filters), false);
|
|
});
|
|
td_->create_handler<GetDialogFiltersQuery>(std::move(promise))->send();
|
|
}
|
|
|
|
void DialogFilterManager::on_get_dialog_filter(telegram_api::object_ptr<telegram_api::DialogFilter> filter) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto new_server_filter = DialogFilter::get_dialog_filter(std::move(filter), true);
|
|
if (new_server_filter == nullptr) {
|
|
return;
|
|
}
|
|
new_server_filter->sort_input_dialog_ids(td_, "on_get_dialog_filter 1");
|
|
|
|
auto dialog_filter_id = new_server_filter->get_dialog_filter_id();
|
|
auto old_filter = get_dialog_filter(dialog_filter_id);
|
|
if (old_filter == nullptr) {
|
|
return;
|
|
}
|
|
bool is_server_changed = false;
|
|
bool is_changed = false;
|
|
for (auto &old_server_filter : server_dialog_filters_) {
|
|
if (old_server_filter->get_dialog_filter_id() == dialog_filter_id && *new_server_filter != *old_server_filter) {
|
|
if (!DialogFilter::are_equivalent(*old_filter, *new_server_filter)) {
|
|
auto new_filter =
|
|
DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter.get(), new_server_filter.get());
|
|
new_filter->sort_input_dialog_ids(td_, "on_get_dialog_filter");
|
|
if (*new_filter != *old_filter) {
|
|
is_changed = true;
|
|
edit_dialog_filter(std::move(new_filter), "on_get_dialog_filter");
|
|
}
|
|
}
|
|
is_server_changed = true;
|
|
old_server_filter = std::move(new_server_filter);
|
|
break;
|
|
}
|
|
}
|
|
if (!is_server_changed) {
|
|
return;
|
|
}
|
|
|
|
if (is_changed || !is_update_chat_filters_sent_) {
|
|
send_update_chat_filters();
|
|
}
|
|
schedule_dialog_filters_reload(get_dialog_filters_cache_time());
|
|
save_dialog_filters();
|
|
|
|
if (need_synchronize_dialog_filters()) {
|
|
synchronize_dialog_filters();
|
|
}
|
|
}
|
|
|
|
void DialogFilterManager::on_get_dialog_filters(
|
|
Result<vector<telegram_api::object_ptr<telegram_api::DialogFilter>>> r_filters, bool dummy) {
|
|
if (G()->close_flag()) {
|
|
return;
|
|
}
|
|
|
|
are_dialog_filters_being_reloaded_ = false;
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto promises = std::move(dialog_filter_reload_queries_);
|
|
dialog_filter_reload_queries_.clear();
|
|
if (r_filters.is_error()) {
|
|
if (!G()->is_expected_error(r_filters.error())) {
|
|
LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery";
|
|
}
|
|
fail_promises(promises, r_filters.move_as_error());
|
|
need_dialog_filters_reload_ = false;
|
|
schedule_dialog_filters_reload(Random::fast(60, 5 * 60));
|
|
return;
|
|
}
|
|
|
|
auto filters = r_filters.move_as_ok();
|
|
vector<unique_ptr<DialogFilter>> new_server_dialog_filters;
|
|
LOG(INFO) << "Receive chat filters from server: " << to_string(filters);
|
|
std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids;
|
|
int32 server_main_dialog_list_position = -1;
|
|
int32 position = 0;
|
|
for (auto &filter : filters) {
|
|
if (filter->get_id() == telegram_api::dialogFilterDefault::ID) {
|
|
if (server_main_dialog_list_position == -1) {
|
|
server_main_dialog_list_position = position;
|
|
} else {
|
|
LOG(ERROR) << "Receive duplicate dialogFilterDefault";
|
|
}
|
|
continue;
|
|
}
|
|
auto dialog_filter = DialogFilter::get_dialog_filter(std::move(filter), true);
|
|
if (dialog_filter == nullptr) {
|
|
continue;
|
|
}
|
|
if (!new_dialog_filter_ids.insert(dialog_filter->get_dialog_filter_id()).second) {
|
|
LOG(ERROR) << "Receive duplicate " << dialog_filter->get_dialog_filter_id();
|
|
continue;
|
|
}
|
|
|
|
dialog_filter->sort_input_dialog_ids(td_, "on_get_dialog_filters 1");
|
|
new_server_dialog_filters.push_back(std::move(dialog_filter));
|
|
position++;
|
|
}
|
|
if (server_main_dialog_list_position == -1) {
|
|
LOG(ERROR) << "Receive no dialogFilterDefault";
|
|
server_main_dialog_list_position = 0;
|
|
}
|
|
if (server_main_dialog_list_position != 0 && !td_->option_manager_->get_option_boolean("is_premium")) {
|
|
LOG(INFO) << "Ignore server main chat list position " << server_main_dialog_list_position;
|
|
server_main_dialog_list_position = 0;
|
|
}
|
|
|
|
bool is_changed = false;
|
|
dialog_filters_updated_date_ = G()->unix_time();
|
|
if (server_dialog_filters_ != new_server_dialog_filters) {
|
|
LOG(INFO) << "Change server chat filters from "
|
|
<< DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
|
|
<< " to "
|
|
<< DialogFilter::get_dialog_filter_ids(new_server_dialog_filters, server_main_dialog_list_position);
|
|
FlatHashMap<DialogFilterId, const DialogFilter *, DialogFilterIdHash> old_server_dialog_filters;
|
|
for (const auto &dialog_filter : server_dialog_filters_) {
|
|
old_server_dialog_filters.emplace(dialog_filter->get_dialog_filter_id(), dialog_filter.get());
|
|
}
|
|
for (const auto &new_server_filter : new_server_dialog_filters) {
|
|
auto dialog_filter_id = new_server_filter->get_dialog_filter_id();
|
|
auto old_filter = get_dialog_filter(dialog_filter_id);
|
|
auto it = old_server_dialog_filters.find(dialog_filter_id);
|
|
if (it != old_server_dialog_filters.end()) {
|
|
auto old_server_filter = it->second;
|
|
if (*new_server_filter != *old_server_filter) {
|
|
if (old_filter == nullptr) {
|
|
// the filter was deleted, don't need to edit it
|
|
} else {
|
|
if (DialogFilter::are_equivalent(*old_filter, *new_server_filter)) { // fast path
|
|
// the filter was edited from this client, nothing to do
|
|
} else {
|
|
auto new_filter =
|
|
DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter, new_server_filter.get());
|
|
new_filter->sort_input_dialog_ids(td_, "on_get_dialog_filters");
|
|
if (*new_filter != *old_filter) {
|
|
is_changed = true;
|
|
edit_dialog_filter(std::move(new_filter), "on_get_dialog_filters");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
old_server_dialog_filters.erase(it);
|
|
} else {
|
|
if (old_filter == nullptr) {
|
|
// the filter was added from another client
|
|
is_changed = true;
|
|
add_dialog_filter(make_unique<DialogFilter>(*new_server_filter), false, "on_get_dialog_filters");
|
|
} else {
|
|
// the filter was added from this client
|
|
// after that it could be added from another client, or edited from this client, or edited from another client
|
|
// prefer local value, so do nothing
|
|
// effectively, ignore edits from other clients, if didn't receive UpdateDialogFilterQuery response
|
|
}
|
|
}
|
|
}
|
|
vector<DialogFilterId> left_old_server_dialog_filter_ids;
|
|
for (const auto &dialog_filter : server_dialog_filters_) {
|
|
if (old_server_dialog_filters.count(dialog_filter->get_dialog_filter_id()) == 0) {
|
|
left_old_server_dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id());
|
|
}
|
|
}
|
|
LOG(INFO) << "Still existing server chat filters: " << left_old_server_dialog_filter_ids;
|
|
for (auto &old_server_filter : old_server_dialog_filters) {
|
|
auto dialog_filter_id = old_server_filter.first;
|
|
// deleted filter
|
|
auto old_filter = get_dialog_filter(dialog_filter_id);
|
|
if (old_filter == nullptr) {
|
|
// the filter was deleted from this client, nothing to do
|
|
} else {
|
|
// the filter was deleted from another client
|
|
// ignore edits done from the current client and just delete the filter
|
|
is_changed = true;
|
|
delete_dialog_filter(dialog_filter_id, "on_get_dialog_filters");
|
|
}
|
|
}
|
|
bool is_order_changed = [&] {
|
|
vector<DialogFilterId> new_server_dialog_filter_ids =
|
|
DialogFilter::get_dialog_filter_ids(new_server_dialog_filters, -1);
|
|
CHECK(new_server_dialog_filter_ids.size() >= left_old_server_dialog_filter_ids.size());
|
|
new_server_dialog_filter_ids.resize(left_old_server_dialog_filter_ids.size());
|
|
return new_server_dialog_filter_ids != left_old_server_dialog_filter_ids;
|
|
}();
|
|
if (is_order_changed) { // if order is changed from this and other clients, prefer order from another client
|
|
vector<DialogFilterId> new_dialog_filter_order;
|
|
for (const auto &new_server_filter : new_server_dialog_filters) {
|
|
auto dialog_filter_id = new_server_filter->get_dialog_filter_id();
|
|
if (get_dialog_filter(dialog_filter_id) != nullptr) {
|
|
new_dialog_filter_order.push_back(dialog_filter_id);
|
|
}
|
|
}
|
|
is_changed = true;
|
|
DialogFilter::set_dialog_filters_order(dialog_filters_, new_dialog_filter_order);
|
|
}
|
|
|
|
server_dialog_filters_ = std::move(new_server_dialog_filters);
|
|
}
|
|
if (server_main_dialog_list_position_ != server_main_dialog_list_position) {
|
|
server_main_dialog_list_position_ = server_main_dialog_list_position;
|
|
|
|
int32 main_dialog_list_position = -1;
|
|
if (server_main_dialog_list_position == 0) {
|
|
main_dialog_list_position = 0;
|
|
} else {
|
|
int32 current_position = 0;
|
|
int32 current_server_position = 0;
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
current_position++;
|
|
if (!dialog_filter->is_empty(true)) {
|
|
current_server_position++;
|
|
}
|
|
if (current_server_position == server_main_dialog_list_position) {
|
|
main_dialog_list_position = current_position;
|
|
}
|
|
}
|
|
if (main_dialog_list_position == -1) {
|
|
LOG(INFO) << "Failed to find server position " << server_main_dialog_list_position << " in chat filters";
|
|
main_dialog_list_position = static_cast<int32>(dialog_filters_.size());
|
|
}
|
|
}
|
|
|
|
if (main_dialog_list_position != main_dialog_list_position_) {
|
|
LOG(INFO) << "Change main chat list position from " << main_dialog_list_position_ << " to "
|
|
<< main_dialog_list_position;
|
|
main_dialog_list_position_ = main_dialog_list_position;
|
|
is_changed = true;
|
|
}
|
|
}
|
|
if (is_changed || !is_update_chat_filters_sent_) {
|
|
send_update_chat_filters();
|
|
}
|
|
schedule_dialog_filters_reload(get_dialog_filters_cache_time());
|
|
save_dialog_filters();
|
|
|
|
if (need_synchronize_dialog_filters()) {
|
|
synchronize_dialog_filters();
|
|
}
|
|
set_promises(promises);
|
|
}
|
|
|
|
bool DialogFilterManager::need_synchronize_dialog_filters() const {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
size_t server_dialog_filter_count = 0;
|
|
vector<DialogFilterId> dialog_filter_ids;
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
if (dialog_filter->is_empty(true)) {
|
|
continue;
|
|
}
|
|
|
|
server_dialog_filter_count++;
|
|
auto server_dialog_filter = get_server_dialog_filter(dialog_filter->get_dialog_filter_id());
|
|
if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
|
|
// need update dialog filter on server
|
|
return true;
|
|
}
|
|
dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id());
|
|
}
|
|
if (server_dialog_filter_count != server_dialog_filters_.size()) {
|
|
// need delete dialog filter on server
|
|
return true;
|
|
}
|
|
if (dialog_filter_ids != DialogFilter::get_dialog_filter_ids(server_dialog_filters_, -1)) {
|
|
// need reorder dialog filters on server
|
|
return true;
|
|
}
|
|
if (get_server_main_dialog_list_position() != server_main_dialog_list_position_) {
|
|
// need reorder main chat list on server
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DialogFilterManager::synchronize_dialog_filters() {
|
|
if (G()->close_flag()) {
|
|
return;
|
|
}
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
|
|
return;
|
|
}
|
|
if (need_dialog_filters_reload_) {
|
|
return reload_dialog_filters();
|
|
}
|
|
if (!need_synchronize_dialog_filters()) {
|
|
// reload filters to repair their order if the server added new filter to the beginning of the list
|
|
return reload_dialog_filters();
|
|
}
|
|
|
|
LOG(INFO) << "Synchronize chat filter changes with server having local "
|
|
<< DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_) << " and server "
|
|
<< DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_);
|
|
for (const auto &server_dialog_filter : server_dialog_filters_) {
|
|
if (get_dialog_filter(server_dialog_filter->get_dialog_filter_id()) == nullptr) {
|
|
return delete_dialog_filter_on_server(server_dialog_filter->get_dialog_filter_id());
|
|
}
|
|
}
|
|
|
|
vector<DialogFilterId> dialog_filter_ids;
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
if (dialog_filter->is_empty(true)) {
|
|
continue;
|
|
}
|
|
|
|
auto server_dialog_filter = get_server_dialog_filter(dialog_filter->get_dialog_filter_id());
|
|
if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
|
|
return update_dialog_filter_on_server(make_unique<DialogFilter>(*dialog_filter));
|
|
}
|
|
dialog_filter_ids.push_back(dialog_filter->get_dialog_filter_id());
|
|
}
|
|
|
|
auto server_main_dialog_list_position = get_server_main_dialog_list_position();
|
|
if (dialog_filter_ids != DialogFilter::get_dialog_filter_ids(server_dialog_filters_, -1) ||
|
|
server_main_dialog_list_position != server_main_dialog_list_position_) {
|
|
return reorder_dialog_filters_on_server(std::move(dialog_filter_ids), server_main_dialog_list_position);
|
|
}
|
|
|
|
UNREACHABLE();
|
|
}
|
|
|
|
void DialogFilterManager::send_update_chat_filters() {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
return;
|
|
}
|
|
|
|
is_update_chat_filters_sent_ = true;
|
|
send_closure(G()->td(), &Td::send_update, get_update_chat_filters_object());
|
|
}
|
|
|
|
td_api::object_ptr<td_api::updateChatFilters> DialogFilterManager::get_update_chat_filters_object() const {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto update = td_api::make_object<td_api::updateChatFilters>();
|
|
for (const auto &dialog_filter : dialog_filters_) {
|
|
update->chat_filters_.push_back(dialog_filter->get_chat_filter_info_object());
|
|
}
|
|
update->main_chat_list_position_ = main_dialog_list_position_;
|
|
return update;
|
|
}
|
|
|
|
void DialogFilterManager::create_dialog_filter(td_api::object_ptr<td_api::chatFilter> filter,
|
|
Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto max_dialog_filters = clamp(td_->option_manager_->get_option_integer("chat_filter_count_max"),
|
|
static_cast<int64>(0), static_cast<int64>(100));
|
|
if (dialog_filters_.size() >= narrow_cast<size_t>(max_dialog_filters)) {
|
|
return promise.set_error(Status::Error(400, "The maximum number of chat folders exceeded"));
|
|
}
|
|
if (!is_update_chat_filters_sent_) {
|
|
return promise.set_error(Status::Error(400, "Chat folders are not synchronized yet"));
|
|
}
|
|
|
|
DialogFilterId dialog_filter_id;
|
|
do {
|
|
auto min_id = static_cast<int>(DialogFilterId::min().get());
|
|
auto max_id = static_cast<int>(DialogFilterId::max().get());
|
|
dialog_filter_id = DialogFilterId(static_cast<int32>(Random::fast(min_id, max_id)));
|
|
} while (get_dialog_filter(dialog_filter_id) != nullptr || get_server_dialog_filter(dialog_filter_id) != nullptr);
|
|
|
|
TRY_RESULT_PROMISE(promise, dialog_filter,
|
|
DialogFilter::create_dialog_filter(td_, dialog_filter_id, std::move(filter)));
|
|
auto chat_filter_info = dialog_filter->get_chat_filter_info_object();
|
|
|
|
bool at_beginning = is_recommended_dialog_filter(dialog_filter.get());
|
|
add_dialog_filter(std::move(dialog_filter), at_beginning, "create_dialog_filter");
|
|
if (at_beginning && main_dialog_list_position_ != 0) {
|
|
main_dialog_list_position_++;
|
|
}
|
|
save_dialog_filters();
|
|
send_update_chat_filters();
|
|
|
|
synchronize_dialog_filters();
|
|
promise.set_value(std::move(chat_filter_info));
|
|
}
|
|
|
|
void DialogFilterManager::edit_dialog_filter(DialogFilterId dialog_filter_id,
|
|
td_api::object_ptr<td_api::chatFilter> filter,
|
|
Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (old_dialog_filter == nullptr) {
|
|
return promise.set_error(Status::Error(400, "Chat filter not found"));
|
|
}
|
|
CHECK(is_update_chat_filters_sent_);
|
|
|
|
TRY_RESULT_PROMISE(promise, new_dialog_filter,
|
|
DialogFilter::create_dialog_filter(td_, dialog_filter_id, std::move(filter)));
|
|
auto chat_filter_info = new_dialog_filter->get_chat_filter_info_object();
|
|
|
|
if (*new_dialog_filter == *old_dialog_filter) {
|
|
return promise.set_value(std::move(chat_filter_info));
|
|
}
|
|
|
|
do_edit_dialog_filter(std::move(new_dialog_filter), true, "edit_dialog_filter");
|
|
|
|
promise.set_value(std::move(chat_filter_info));
|
|
}
|
|
|
|
void DialogFilterManager::do_edit_dialog_filter(unique_ptr<DialogFilter> &&filter, bool need_synchronize,
|
|
const char *source) {
|
|
edit_dialog_filter(std::move(filter), source);
|
|
save_dialog_filters();
|
|
send_update_chat_filters();
|
|
|
|
if (need_synchronize) {
|
|
synchronize_dialog_filters();
|
|
}
|
|
}
|
|
|
|
void DialogFilterManager::update_dialog_filter_on_server(unique_ptr<DialogFilter> &&dialog_filter) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
CHECK(dialog_filter != nullptr);
|
|
are_dialog_filters_being_synchronized_ = true;
|
|
dialog_filter->remove_secret_chat_dialog_ids();
|
|
auto dialog_filter_id = dialog_filter->get_dialog_filter_id();
|
|
auto input_dialog_filter = dialog_filter->get_input_dialog_filter();
|
|
|
|
auto promise = PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), dialog_filter = std::move(dialog_filter)](Result<Unit> result) mutable {
|
|
send_closure(actor_id, &DialogFilterManager::on_update_dialog_filter, std::move(dialog_filter),
|
|
result.is_error() ? result.move_as_error() : Status::OK());
|
|
});
|
|
td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))
|
|
->send(dialog_filter_id, std::move(input_dialog_filter));
|
|
}
|
|
|
|
void DialogFilterManager::on_update_dialog_filter(unique_ptr<DialogFilter> dialog_filter, Status result) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (result.is_error()) {
|
|
// TODO rollback dialog_filters_ changes if error isn't 429
|
|
} else {
|
|
bool is_edited = false;
|
|
for (auto &server_dialog_filter : server_dialog_filters_) {
|
|
if (server_dialog_filter->get_dialog_filter_id() == dialog_filter->get_dialog_filter_id()) {
|
|
if (*server_dialog_filter != *dialog_filter) {
|
|
server_dialog_filter = std::move(dialog_filter);
|
|
}
|
|
is_edited = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!is_edited) {
|
|
bool at_beginning = is_recommended_dialog_filter(dialog_filter.get());
|
|
if (at_beginning) {
|
|
server_dialog_filters_.insert(server_dialog_filters_.begin(), std::move(dialog_filter));
|
|
} else {
|
|
server_dialog_filters_.push_back(std::move(dialog_filter));
|
|
}
|
|
if (at_beginning && server_main_dialog_list_position_ != 0) {
|
|
server_main_dialog_list_position_++;
|
|
}
|
|
}
|
|
save_dialog_filters();
|
|
}
|
|
|
|
are_dialog_filters_being_synchronized_ = false;
|
|
synchronize_dialog_filters();
|
|
}
|
|
|
|
void DialogFilterManager::delete_dialog_filter(DialogFilterId dialog_filter_id, Promise<Unit> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
auto dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return promise.set_value(Unit());
|
|
}
|
|
|
|
int32 position = delete_dialog_filter(dialog_filter_id, "delete_dialog_filter");
|
|
if (main_dialog_list_position_ > position) {
|
|
main_dialog_list_position_--;
|
|
}
|
|
save_dialog_filters();
|
|
send_update_chat_filters();
|
|
|
|
synchronize_dialog_filters();
|
|
promise.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::delete_dialog_filter_on_server(DialogFilterId dialog_filter_id) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
are_dialog_filters_being_synchronized_ = true;
|
|
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id](Result<Unit> result) {
|
|
send_closure(actor_id, &DialogFilterManager::on_delete_dialog_filter, dialog_filter_id,
|
|
result.is_error() ? result.move_as_error() : Status::OK());
|
|
});
|
|
td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))->send(dialog_filter_id, nullptr);
|
|
}
|
|
|
|
void DialogFilterManager::on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (result.is_error()) {
|
|
// TODO rollback dialog_filters_ changes if error isn't 429
|
|
} else {
|
|
for (auto it = server_dialog_filters_.begin(); it != server_dialog_filters_.end(); ++it) {
|
|
if ((*it)->get_dialog_filter_id() == dialog_filter_id) {
|
|
server_dialog_filters_.erase(it);
|
|
save_dialog_filters();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
are_dialog_filters_being_synchronized_ = false;
|
|
synchronize_dialog_filters();
|
|
}
|
|
|
|
void DialogFilterManager::reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids,
|
|
int32 main_dialog_list_position, Promise<Unit> &&promise) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
|
|
for (auto dialog_filter_id : dialog_filter_ids) {
|
|
auto dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return promise.set_error(Status::Error(400, "Chat filter not found"));
|
|
}
|
|
}
|
|
std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids_set(dialog_filter_ids.begin(),
|
|
dialog_filter_ids.end());
|
|
if (new_dialog_filter_ids_set.size() != dialog_filter_ids.size()) {
|
|
return promise.set_error(Status::Error(400, "Duplicate chat filters in the new list"));
|
|
}
|
|
if (main_dialog_list_position < 0 || main_dialog_list_position > static_cast<int32>(dialog_filters_.size())) {
|
|
return promise.set_error(Status::Error(400, "Invalid main chat list position specified"));
|
|
}
|
|
if (!td_->option_manager_->get_option_boolean("is_premium")) {
|
|
main_dialog_list_position = 0;
|
|
}
|
|
|
|
if (DialogFilter::set_dialog_filters_order(dialog_filters_, dialog_filter_ids) ||
|
|
main_dialog_list_position != main_dialog_list_position_) {
|
|
main_dialog_list_position_ = main_dialog_list_position;
|
|
|
|
save_dialog_filters();
|
|
send_update_chat_filters();
|
|
|
|
synchronize_dialog_filters();
|
|
}
|
|
promise.set_value(Unit());
|
|
}
|
|
|
|
void DialogFilterManager::create_dialog_filter_invite_link(
|
|
DialogFilterId dialog_filter_id, string invite_link_name, vector<DialogId> dialog_ids,
|
|
Promise<td_api::object_ptr<td_api::chatFilterInviteLink>> promise) {
|
|
auto dialog_filter = get_dialog_filter(dialog_filter_id);
|
|
if (dialog_filter == nullptr) {
|
|
return promise.set_error(Status::Error(400, "Chat filter not found"));
|
|
}
|
|
vector<tl_object_ptr<telegram_api::InputPeer>> input_peers;
|
|
input_peers.reserve(dialog_ids.size());
|
|
for (auto &dialog_id : dialog_ids) {
|
|
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
|
|
if (input_peer == nullptr) {
|
|
return promise.set_error(Status::Error(400, "Have no access to the chat"));
|
|
}
|
|
input_peers.push_back(std::move(input_peer));
|
|
}
|
|
if (input_peers.empty()) {
|
|
return promise.set_error(Status::Error(400, "At least one chat must be included"));
|
|
}
|
|
td_->create_handler<ExportChatlistInviteQuery>(std::move(promise))
|
|
->send(dialog_filter_id, invite_link_name, std::move(input_peers));
|
|
}
|
|
|
|
void DialogFilterManager::reorder_dialog_filters_on_server(vector<DialogFilterId> dialog_filter_ids,
|
|
int32 main_dialog_list_position) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
are_dialog_filters_being_synchronized_ = true;
|
|
auto promise = PromiseCreator::lambda(
|
|
[actor_id = actor_id(this), dialog_filter_ids, main_dialog_list_position](Result<Unit> result) mutable {
|
|
send_closure(actor_id, &DialogFilterManager::on_reorder_dialog_filters, std::move(dialog_filter_ids),
|
|
main_dialog_list_position, result.is_error() ? result.move_as_error() : Status::OK());
|
|
});
|
|
td_->create_handler<UpdateDialogFiltersOrderQuery>(std::move(promise))
|
|
->send(dialog_filter_ids, main_dialog_list_position);
|
|
}
|
|
|
|
void DialogFilterManager::on_reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids,
|
|
int32 main_dialog_list_position, Status result) {
|
|
CHECK(!td_->auth_manager_->is_bot());
|
|
if (result.is_error()) {
|
|
// TODO rollback dialog_filters_ changes if error isn't 429
|
|
} else {
|
|
if (DialogFilter::set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids)) ||
|
|
server_main_dialog_list_position_ != main_dialog_list_position) {
|
|
server_main_dialog_list_position_ = main_dialog_list_position;
|
|
save_dialog_filters();
|
|
}
|
|
}
|
|
|
|
are_dialog_filters_being_synchronized_ = false;
|
|
synchronize_dialog_filters();
|
|
}
|
|
|
|
void DialogFilterManager::add_dialog_filter(unique_ptr<DialogFilter> dialog_filter, bool at_beginning,
|
|
const char *source) {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
// just in case
|
|
return;
|
|
}
|
|
|
|
CHECK(dialog_filter != nullptr);
|
|
auto dialog_filter_id = dialog_filter->get_dialog_filter_id();
|
|
LOG(INFO) << "Add " << dialog_filter_id << " from " << source;
|
|
CHECK(get_dialog_filter(dialog_filter_id) == nullptr);
|
|
if (at_beginning) {
|
|
dialog_filters_.insert(dialog_filters_.begin(), std::move(dialog_filter));
|
|
} else {
|
|
dialog_filters_.push_back(std::move(dialog_filter));
|
|
}
|
|
|
|
td_->messages_manager_->add_dialog_list_for_dialog_filter(dialog_filter_id);
|
|
}
|
|
|
|
void DialogFilterManager::edit_dialog_filter(unique_ptr<DialogFilter> new_dialog_filter, const char *source) {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
// just in case
|
|
return;
|
|
}
|
|
|
|
CHECK(new_dialog_filter != nullptr);
|
|
LOG(INFO) << "Edit " << new_dialog_filter->get_dialog_filter_id() << " from " << source;
|
|
for (auto &old_dialog_filter : dialog_filters_) {
|
|
if (old_dialog_filter->get_dialog_filter_id() == new_dialog_filter->get_dialog_filter_id()) {
|
|
CHECK(*old_dialog_filter != *new_dialog_filter);
|
|
|
|
disable_get_dialog_filter_ = true; // to ensure crash if get_dialog_filter is called
|
|
|
|
td_->messages_manager_->edit_dialog_list_for_dialog_filter(old_dialog_filter, std::move(new_dialog_filter),
|
|
disable_get_dialog_filter_, source);
|
|
return;
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
int32 DialogFilterManager::delete_dialog_filter(DialogFilterId dialog_filter_id, const char *source) {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
// just in case
|
|
return -1;
|
|
}
|
|
|
|
LOG(INFO) << "Delete " << dialog_filter_id << " from " << source;
|
|
for (auto it = dialog_filters_.begin(); it != dialog_filters_.end(); ++it) {
|
|
if ((*it)->get_dialog_filter_id() == dialog_filter_id) {
|
|
auto position = static_cast<int32>(it - dialog_filters_.begin());
|
|
td_->messages_manager_->delete_dialog_list_for_dialog_filter(dialog_filter_id, source);
|
|
dialog_filters_.erase(it);
|
|
return position;
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
return -1;
|
|
}
|
|
|
|
void DialogFilterManager::save_dialog_filters() {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
return;
|
|
}
|
|
|
|
DialogFiltersLogEvent log_event;
|
|
log_event.server_main_dialog_list_position = server_main_dialog_list_position_;
|
|
log_event.main_dialog_list_position = main_dialog_list_position_;
|
|
log_event.updated_date = dialog_filters_updated_date_;
|
|
log_event.server_dialog_filters_in = &server_dialog_filters_;
|
|
log_event.dialog_filters_in = &dialog_filters_;
|
|
|
|
LOG(INFO) << "Save server chat filters "
|
|
<< DialogFilter::get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
|
|
<< " and local chat filters "
|
|
<< DialogFilter::get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
|
|
|
|
G()->td_db()->get_binlog_pmc()->set("dialog_filters", log_event_store(log_event).as_slice().str());
|
|
}
|
|
|
|
void DialogFilterManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
|
|
if (have_dialog_filters()) {
|
|
updates.push_back(get_update_chat_filters_object());
|
|
}
|
|
}
|
|
|
|
} // namespace td
|