771 lines
32 KiB
C++
771 lines
32 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/DialogFilter.h"
|
|
|
|
#include "td/telegram/ContactsManager.h"
|
|
#include "td/telegram/DialogId.h"
|
|
#include "td/telegram/Global.h"
|
|
#include "td/telegram/MessagesManager.h"
|
|
#include "td/telegram/misc.h"
|
|
#include "td/telegram/Td.h"
|
|
|
|
#include "td/utils/algorithm.h"
|
|
#include "td/utils/emoji.h"
|
|
#include "td/utils/FlatHashSet.h"
|
|
#include "td/utils/format.h"
|
|
#include "td/utils/logging.h"
|
|
#include "td/utils/misc.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace td {
|
|
|
|
int32 DialogFilter::get_max_filter_dialogs() {
|
|
return narrow_cast<int32>(G()->get_option_integer("chat_filter_chosen_chat_count_max", 100));
|
|
}
|
|
|
|
unique_ptr<DialogFilter> DialogFilter::get_dialog_filter(
|
|
telegram_api::object_ptr<telegram_api::DialogFilter> filter_ptr, bool with_id) {
|
|
if (filter_ptr->get_id() != telegram_api::dialogFilter::ID) {
|
|
LOG(ERROR) << "Ignore " << to_string(filter_ptr);
|
|
return nullptr;
|
|
}
|
|
auto filter = telegram_api::move_object_as<telegram_api::dialogFilter>(filter_ptr);
|
|
DialogFilterId dialog_filter_id(filter->id_);
|
|
if (with_id && !dialog_filter_id.is_valid()) {
|
|
LOG(ERROR) << "Receive invalid " << to_string(filter);
|
|
return nullptr;
|
|
}
|
|
auto dialog_filter = make_unique<DialogFilter>();
|
|
dialog_filter->dialog_filter_id = dialog_filter_id;
|
|
dialog_filter->title = std::move(filter->title_);
|
|
dialog_filter->emoji = std::move(filter->emoticon_);
|
|
FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
|
|
dialog_filter->pinned_dialog_ids = InputDialogId::get_input_dialog_ids(filter->pinned_peers_, &added_dialog_ids);
|
|
dialog_filter->included_dialog_ids = InputDialogId::get_input_dialog_ids(filter->include_peers_, &added_dialog_ids);
|
|
dialog_filter->excluded_dialog_ids = InputDialogId::get_input_dialog_ids(filter->exclude_peers_, &added_dialog_ids);
|
|
auto flags = filter->flags_;
|
|
dialog_filter->exclude_muted = (flags & telegram_api::dialogFilter::EXCLUDE_MUTED_MASK) != 0;
|
|
dialog_filter->exclude_read = (flags & telegram_api::dialogFilter::EXCLUDE_READ_MASK) != 0;
|
|
dialog_filter->exclude_archived = (flags & telegram_api::dialogFilter::EXCLUDE_ARCHIVED_MASK) != 0;
|
|
dialog_filter->include_contacts = (flags & telegram_api::dialogFilter::CONTACTS_MASK) != 0;
|
|
dialog_filter->include_non_contacts = (flags & telegram_api::dialogFilter::NON_CONTACTS_MASK) != 0;
|
|
dialog_filter->include_bots = (flags & telegram_api::dialogFilter::BOTS_MASK) != 0;
|
|
dialog_filter->include_groups = (flags & telegram_api::dialogFilter::GROUPS_MASK) != 0;
|
|
dialog_filter->include_channels = (flags & telegram_api::dialogFilter::BROADCASTS_MASK) != 0;
|
|
return dialog_filter;
|
|
}
|
|
|
|
Result<unique_ptr<DialogFilter>> DialogFilter::create_dialog_filter(Td *td, DialogFilterId dialog_filter_id,
|
|
td_api::object_ptr<td_api::chatFilter> filter) {
|
|
CHECK(filter != nullptr);
|
|
|
|
auto dialog_filter = make_unique<DialogFilter>();
|
|
dialog_filter->dialog_filter_id = dialog_filter_id;
|
|
|
|
FlatHashSet<int64> added_dialog_ids;
|
|
auto add_chats = [td, &added_dialog_ids](vector<InputDialogId> &input_dialog_ids, const vector<int64> &chat_ids) {
|
|
for (const auto &chat_id : chat_ids) {
|
|
if (!added_dialog_ids.insert(chat_id).second) {
|
|
// do not allow duplicate chat_ids
|
|
continue;
|
|
}
|
|
|
|
input_dialog_ids.push_back(td->messages_manager_->get_input_dialog_id(DialogId(chat_id)));
|
|
}
|
|
};
|
|
add_chats(dialog_filter->pinned_dialog_ids, filter->pinned_chat_ids_);
|
|
add_chats(dialog_filter->included_dialog_ids, filter->included_chat_ids_);
|
|
add_chats(dialog_filter->excluded_dialog_ids, filter->excluded_chat_ids_);
|
|
|
|
constexpr size_t MAX_TITLE_LENGTH = 12; // server-side limit for dialog filter title
|
|
dialog_filter->title = clean_name(std::move(filter->title_), MAX_TITLE_LENGTH);
|
|
if (dialog_filter->title.empty()) {
|
|
return Status::Error(400, "Title must be non-empty");
|
|
}
|
|
dialog_filter->emoji = get_emoji_by_icon_name(filter->icon_name_);
|
|
if (dialog_filter->emoji.empty() && !filter->icon_name_.empty()) {
|
|
return Status::Error(400, "Invalid icon name specified");
|
|
}
|
|
dialog_filter->exclude_muted = filter->exclude_muted_;
|
|
dialog_filter->exclude_read = filter->exclude_read_;
|
|
dialog_filter->exclude_archived = filter->exclude_archived_;
|
|
dialog_filter->include_contacts = filter->include_contacts_;
|
|
dialog_filter->include_non_contacts = filter->include_non_contacts_;
|
|
dialog_filter->include_bots = filter->include_bots_;
|
|
dialog_filter->include_groups = filter->include_groups_;
|
|
dialog_filter->include_channels = filter->include_channels_;
|
|
|
|
TRY_STATUS(dialog_filter->check_limits());
|
|
dialog_filter->sort_input_dialog_ids(td, "create_dialog_filter");
|
|
|
|
return std::move(dialog_filter);
|
|
}
|
|
|
|
void DialogFilter::set_dialog_is_pinned(InputDialogId input_dialog_id, bool is_pinned) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
if (is_pinned) {
|
|
pinned_dialog_ids.insert(pinned_dialog_ids.begin(), input_dialog_id);
|
|
InputDialogId::remove(included_dialog_ids, dialog_id);
|
|
InputDialogId::remove(excluded_dialog_ids, dialog_id);
|
|
} else {
|
|
bool is_removed = InputDialogId::remove(pinned_dialog_ids, dialog_id);
|
|
CHECK(is_removed);
|
|
included_dialog_ids.push_back(input_dialog_id);
|
|
}
|
|
}
|
|
|
|
void DialogFilter::set_pinned_dialog_ids(vector<InputDialogId> &&input_dialog_ids) {
|
|
FlatHashSet<DialogId, DialogIdHash> new_pinned_dialog_ids;
|
|
for (auto input_dialog_id : input_dialog_ids) {
|
|
new_pinned_dialog_ids.insert(input_dialog_id.get_dialog_id());
|
|
}
|
|
|
|
auto old_pinned_dialog_ids = std::move(pinned_dialog_ids);
|
|
pinned_dialog_ids = std::move(input_dialog_ids);
|
|
auto is_new_pinned = [&new_pinned_dialog_ids](InputDialogId input_dialog_id) {
|
|
return new_pinned_dialog_ids.count(input_dialog_id.get_dialog_id()) > 0;
|
|
};
|
|
td::remove_if(old_pinned_dialog_ids, is_new_pinned);
|
|
td::remove_if(included_dialog_ids, is_new_pinned);
|
|
td::remove_if(excluded_dialog_ids, is_new_pinned);
|
|
append(included_dialog_ids, old_pinned_dialog_ids);
|
|
}
|
|
|
|
void DialogFilter::include_dialog(InputDialogId input_dialog_id) {
|
|
included_dialog_ids.push_back(input_dialog_id);
|
|
InputDialogId::remove(excluded_dialog_ids, input_dialog_id.get_dialog_id());
|
|
}
|
|
|
|
void DialogFilter::remove_secret_chat_dialog_ids() {
|
|
auto remove_secret_chats = [](vector<InputDialogId> &input_dialog_ids) {
|
|
td::remove_if(input_dialog_ids, [](InputDialogId input_dialog_id) {
|
|
return input_dialog_id.get_dialog_id().get_type() == DialogType::SecretChat;
|
|
});
|
|
};
|
|
remove_secret_chats(pinned_dialog_ids);
|
|
remove_secret_chats(included_dialog_ids);
|
|
remove_secret_chats(excluded_dialog_ids);
|
|
}
|
|
|
|
void DialogFilter::remove_dialog_id(DialogId dialog_id) {
|
|
InputDialogId::remove(pinned_dialog_ids, dialog_id);
|
|
InputDialogId::remove(included_dialog_ids, dialog_id);
|
|
InputDialogId::remove(excluded_dialog_ids, dialog_id);
|
|
}
|
|
|
|
bool DialogFilter::is_empty(bool for_server) const {
|
|
if (include_contacts || include_non_contacts || include_bots || include_groups || include_channels) {
|
|
return false;
|
|
}
|
|
|
|
if (for_server) {
|
|
vector<InputDialogId> empty_input_dialog_ids;
|
|
return InputDialogId::are_equivalent(pinned_dialog_ids, empty_input_dialog_ids) &&
|
|
InputDialogId::are_equivalent(included_dialog_ids, empty_input_dialog_ids);
|
|
} else {
|
|
return pinned_dialog_ids.empty() && included_dialog_ids.empty();
|
|
}
|
|
}
|
|
|
|
bool DialogFilter::can_include_dialog(DialogId dialog_id) const {
|
|
if (InputDialogId::contains(included_dialog_ids, dialog_id) ||
|
|
InputDialogId::contains(pinned_dialog_ids, dialog_id)) {
|
|
// the dialog is already included
|
|
return false;
|
|
}
|
|
|
|
if (included_dialog_ids.size() + pinned_dialog_ids.size() < narrow_cast<size_t>(get_max_filter_dialogs())) {
|
|
// fast path
|
|
return true;
|
|
}
|
|
|
|
auto new_dialog_filter = make_unique<DialogFilter>(*this);
|
|
new_dialog_filter->include_dialog(InputDialogId(dialog_id));
|
|
return new_dialog_filter->check_limits().is_ok();
|
|
}
|
|
|
|
Status DialogFilter::check_limits() const {
|
|
auto get_server_dialog_count = [](const vector<InputDialogId> &input_dialog_ids) {
|
|
int32 result = 0;
|
|
for (auto &input_dialog_id : input_dialog_ids) {
|
|
if (input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat) {
|
|
result++;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
auto excluded_server_dialog_count = get_server_dialog_count(excluded_dialog_ids);
|
|
auto included_server_dialog_count = get_server_dialog_count(included_dialog_ids);
|
|
auto pinned_server_dialog_count = get_server_dialog_count(pinned_dialog_ids);
|
|
|
|
auto excluded_secret_dialog_count = static_cast<int32>(excluded_dialog_ids.size()) - excluded_server_dialog_count;
|
|
auto included_secret_dialog_count = static_cast<int32>(included_dialog_ids.size()) - included_server_dialog_count;
|
|
auto pinned_secret_dialog_count = static_cast<int32>(pinned_dialog_ids.size()) - pinned_server_dialog_count;
|
|
|
|
auto limit = get_max_filter_dialogs();
|
|
if (excluded_server_dialog_count > limit || excluded_secret_dialog_count > limit) {
|
|
return Status::Error(400, "The maximum number of excluded chats exceeded");
|
|
}
|
|
if (included_server_dialog_count > limit || included_secret_dialog_count > limit) {
|
|
return Status::Error(400, "The maximum number of included chats exceeded");
|
|
}
|
|
if (included_server_dialog_count + pinned_server_dialog_count > limit ||
|
|
included_secret_dialog_count + pinned_secret_dialog_count > limit) {
|
|
return Status::Error(400, "The maximum number of pinned chats exceeded");
|
|
}
|
|
|
|
if (is_empty(false)) {
|
|
return Status::Error(400, "Folder must contain at least 1 chat");
|
|
}
|
|
|
|
if (include_contacts && include_non_contacts && include_bots && include_groups && include_channels &&
|
|
exclude_archived && !exclude_read && !exclude_muted) {
|
|
return Status::Error(400, "Folder must be different from the main chat list");
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
string DialogFilter::get_emoji_by_icon_name(const string &icon_name) {
|
|
init_icon_names();
|
|
auto it = icon_name_to_emoji_.find(icon_name);
|
|
if (it != icon_name_to_emoji_.end()) {
|
|
return it->second;
|
|
}
|
|
return string();
|
|
}
|
|
|
|
string DialogFilter::get_icon_name() const {
|
|
init_icon_names();
|
|
auto it = emoji_to_icon_name_.find(emoji);
|
|
if (it != emoji_to_icon_name_.end()) {
|
|
return it->second;
|
|
}
|
|
return string();
|
|
}
|
|
|
|
string DialogFilter::get_chosen_or_default_icon_name() const {
|
|
auto icon_name = get_icon_name();
|
|
if (!icon_name.empty()) {
|
|
return icon_name;
|
|
}
|
|
|
|
if (!pinned_dialog_ids.empty() || !included_dialog_ids.empty() || !excluded_dialog_ids.empty()) {
|
|
return "Custom";
|
|
}
|
|
|
|
if (include_contacts || include_non_contacts) {
|
|
if (!include_bots && !include_groups && !include_channels) {
|
|
return "Private";
|
|
}
|
|
} else {
|
|
if (!include_bots && !include_channels) {
|
|
if (!include_groups) {
|
|
// just in case
|
|
return "Custom";
|
|
}
|
|
return "Groups";
|
|
}
|
|
if (!include_bots && !include_groups) {
|
|
return "Channels";
|
|
}
|
|
if (!include_groups && !include_channels) {
|
|
return "Bots";
|
|
}
|
|
}
|
|
if (exclude_read && !exclude_muted) {
|
|
return "Unread";
|
|
}
|
|
if (exclude_muted && !exclude_read) {
|
|
return "Unmuted";
|
|
}
|
|
return "Custom";
|
|
}
|
|
|
|
string DialogFilter::get_default_icon_name(const td_api::chatFilter *filter) {
|
|
if (!filter->icon_name_.empty() && !get_emoji_by_icon_name(filter->icon_name_).empty()) {
|
|
return filter->icon_name_;
|
|
}
|
|
|
|
if (!filter->pinned_chat_ids_.empty() || !filter->included_chat_ids_.empty() || !filter->excluded_chat_ids_.empty()) {
|
|
return "Custom";
|
|
}
|
|
|
|
if (filter->include_contacts_ || filter->include_non_contacts_) {
|
|
if (!filter->include_bots_ && !filter->include_groups_ && !filter->include_channels_) {
|
|
return "Private";
|
|
}
|
|
} else {
|
|
if (!filter->include_bots_ && !filter->include_channels_) {
|
|
if (!filter->include_groups_) {
|
|
// just in case
|
|
return "Custom";
|
|
}
|
|
return "Groups";
|
|
}
|
|
if (!filter->include_bots_ && !filter->include_groups_) {
|
|
return "Channels";
|
|
}
|
|
if (!filter->include_groups_ && !filter->include_channels_) {
|
|
return "Bots";
|
|
}
|
|
}
|
|
if (filter->exclude_read_ && !filter->exclude_muted_) {
|
|
return "Unread";
|
|
}
|
|
if (filter->exclude_muted_ && !filter->exclude_read_) {
|
|
return "Unmuted";
|
|
}
|
|
return "Custom";
|
|
}
|
|
|
|
telegram_api::object_ptr<telegram_api::DialogFilter> DialogFilter::get_input_dialog_filter() const {
|
|
int32 flags = 0;
|
|
if (!emoji.empty()) {
|
|
flags |= telegram_api::dialogFilter::EMOTICON_MASK;
|
|
}
|
|
if (exclude_muted) {
|
|
flags |= telegram_api::dialogFilter::EXCLUDE_MUTED_MASK;
|
|
}
|
|
if (exclude_read) {
|
|
flags |= telegram_api::dialogFilter::EXCLUDE_READ_MASK;
|
|
}
|
|
if (exclude_archived) {
|
|
flags |= telegram_api::dialogFilter::EXCLUDE_ARCHIVED_MASK;
|
|
}
|
|
if (include_contacts) {
|
|
flags |= telegram_api::dialogFilter::CONTACTS_MASK;
|
|
}
|
|
if (include_non_contacts) {
|
|
flags |= telegram_api::dialogFilter::NON_CONTACTS_MASK;
|
|
}
|
|
if (include_bots) {
|
|
flags |= telegram_api::dialogFilter::BOTS_MASK;
|
|
}
|
|
if (include_groups) {
|
|
flags |= telegram_api::dialogFilter::GROUPS_MASK;
|
|
}
|
|
if (include_channels) {
|
|
flags |= telegram_api::dialogFilter::BROADCASTS_MASK;
|
|
}
|
|
|
|
return telegram_api::make_object<telegram_api::dialogFilter>(
|
|
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
|
|
false /*ignored*/, false /*ignored*/, false /*ignored*/, dialog_filter_id.get(), title, emoji,
|
|
InputDialogId::get_input_peers(pinned_dialog_ids), InputDialogId::get_input_peers(included_dialog_ids),
|
|
InputDialogId::get_input_peers(excluded_dialog_ids));
|
|
}
|
|
|
|
td_api::object_ptr<td_api::chatFilter> DialogFilter::get_chat_filter_object(
|
|
const vector<DialogId> &unknown_dialog_ids) const {
|
|
auto get_chat_ids = [unknown_dialog_ids](const vector<InputDialogId> &input_dialog_ids) {
|
|
vector<int64> chat_ids;
|
|
chat_ids.reserve(input_dialog_ids.size());
|
|
for (auto &input_dialog_id : input_dialog_ids) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
if (!td::contains(unknown_dialog_ids, dialog_id)) {
|
|
chat_ids.push_back(dialog_id.get());
|
|
}
|
|
}
|
|
return chat_ids;
|
|
};
|
|
|
|
return td_api::make_object<td_api::chatFilter>(title, get_icon_name(), get_chat_ids(pinned_dialog_ids),
|
|
get_chat_ids(included_dialog_ids), get_chat_ids(excluded_dialog_ids),
|
|
exclude_muted, exclude_read, exclude_archived, include_contacts,
|
|
include_non_contacts, include_bots, include_groups, include_channels);
|
|
}
|
|
|
|
td_api::object_ptr<td_api::chatFilterInfo> DialogFilter::get_chat_filter_info_object() const {
|
|
return td_api::make_object<td_api::chatFilterInfo>(dialog_filter_id.get(), title, get_chosen_or_default_icon_name());
|
|
}
|
|
|
|
void DialogFilter::for_each_dialog(std::function<void(const InputDialogId &)> callback) const {
|
|
for (auto input_dialog_ids : {&pinned_dialog_ids, &excluded_dialog_ids, &included_dialog_ids}) {
|
|
for (const auto &input_dialog_id : *input_dialog_ids) {
|
|
callback(input_dialog_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// merges changes from old_server_filter to new_server_filter in old_filter
|
|
unique_ptr<DialogFilter> DialogFilter::merge_dialog_filter_changes(const DialogFilter *old_filter,
|
|
const DialogFilter *old_server_filter,
|
|
const DialogFilter *new_server_filter) {
|
|
CHECK(old_filter != nullptr);
|
|
CHECK(old_server_filter != nullptr);
|
|
CHECK(new_server_filter != nullptr);
|
|
CHECK(old_filter->dialog_filter_id == old_server_filter->dialog_filter_id);
|
|
CHECK(old_filter->dialog_filter_id == new_server_filter->dialog_filter_id);
|
|
auto dialog_filter_id = old_filter->dialog_filter_id;
|
|
auto new_filter = make_unique<DialogFilter>(*old_filter);
|
|
new_filter->dialog_filter_id = dialog_filter_id;
|
|
|
|
auto merge_ordered_changes = [dialog_filter_id](auto &new_dialog_ids, auto old_server_dialog_ids,
|
|
auto new_server_dialog_ids) {
|
|
if (old_server_dialog_ids == new_server_dialog_ids) {
|
|
LOG(INFO) << "Pinned chats was not changed remotely in " << dialog_filter_id << ", keep local changes";
|
|
return;
|
|
}
|
|
|
|
if (InputDialogId::are_equivalent(new_dialog_ids, old_server_dialog_ids)) {
|
|
LOG(INFO) << "Pinned chats was not changed locally in " << dialog_filter_id << ", keep remote changes";
|
|
|
|
size_t kept_server_dialogs = 0;
|
|
FlatHashSet<DialogId, DialogIdHash> removed_dialog_ids;
|
|
auto old_it = old_server_dialog_ids.rbegin();
|
|
for (auto &input_dialog_id : reversed(new_server_dialog_ids)) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
while (old_it < old_server_dialog_ids.rend()) {
|
|
if (old_it->get_dialog_id() == dialog_id) {
|
|
kept_server_dialogs++;
|
|
++old_it;
|
|
break;
|
|
}
|
|
|
|
// remove the dialog, it could be added back later
|
|
CHECK(old_it->get_dialog_id().is_valid());
|
|
removed_dialog_ids.insert(old_it->get_dialog_id());
|
|
++old_it;
|
|
}
|
|
}
|
|
while (old_it < old_server_dialog_ids.rend()) {
|
|
// remove the dialog, it could be added back later
|
|
CHECK(old_it->get_dialog_id().is_valid());
|
|
removed_dialog_ids.insert(old_it->get_dialog_id());
|
|
++old_it;
|
|
}
|
|
td::remove_if(new_dialog_ids, [&removed_dialog_ids](auto input_dialog_id) {
|
|
return removed_dialog_ids.count(input_dialog_id.get_dialog_id()) > 0;
|
|
});
|
|
new_dialog_ids.insert(new_dialog_ids.begin(), new_server_dialog_ids.begin(),
|
|
new_server_dialog_ids.end() - kept_server_dialogs);
|
|
} else {
|
|
LOG(WARNING) << "Ignore remote changes of pinned chats in " << dialog_filter_id;
|
|
// there are both local and remote changes; ignore remote changes for now
|
|
}
|
|
};
|
|
|
|
auto merge_changes = [](auto &new_dialog_ids, const auto &old_server_dialog_ids, const auto &new_server_dialog_ids) {
|
|
if (old_server_dialog_ids == new_server_dialog_ids) {
|
|
// fast path
|
|
return;
|
|
}
|
|
|
|
// merge additions and deletions from other clients to the local changes
|
|
FlatHashSet<DialogId, DialogIdHash> deleted_dialog_ids;
|
|
for (const auto &old_dialog_id : old_server_dialog_ids) {
|
|
CHECK(old_dialog_id.get_dialog_id().is_valid());
|
|
deleted_dialog_ids.insert(old_dialog_id.get_dialog_id());
|
|
}
|
|
FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
|
|
for (const auto &new_dialog_id : new_server_dialog_ids) {
|
|
auto dialog_id = new_dialog_id.get_dialog_id();
|
|
if (deleted_dialog_ids.erase(dialog_id) == 0) {
|
|
added_dialog_ids.insert(dialog_id);
|
|
}
|
|
}
|
|
vector<InputDialogId> result;
|
|
for (const auto &input_dialog_id : new_dialog_ids) {
|
|
// do not add dialog twice
|
|
added_dialog_ids.erase(input_dialog_id.get_dialog_id());
|
|
}
|
|
for (const auto &new_dialog_id : new_server_dialog_ids) {
|
|
if (added_dialog_ids.count(new_dialog_id.get_dialog_id()) == 1) {
|
|
result.push_back(new_dialog_id);
|
|
}
|
|
}
|
|
for (const auto &input_dialog_id : new_dialog_ids) {
|
|
if (deleted_dialog_ids.count(input_dialog_id.get_dialog_id()) == 0) {
|
|
result.push_back(input_dialog_id);
|
|
}
|
|
}
|
|
new_dialog_ids = std::move(result);
|
|
};
|
|
|
|
merge_ordered_changes(new_filter->pinned_dialog_ids, old_server_filter->pinned_dialog_ids,
|
|
new_server_filter->pinned_dialog_ids);
|
|
merge_changes(new_filter->included_dialog_ids, old_server_filter->included_dialog_ids,
|
|
new_server_filter->included_dialog_ids);
|
|
merge_changes(new_filter->excluded_dialog_ids, old_server_filter->excluded_dialog_ids,
|
|
new_server_filter->excluded_dialog_ids);
|
|
|
|
{
|
|
FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
|
|
auto remove_duplicates = [&added_dialog_ids](auto &input_dialog_ids) {
|
|
td::remove_if(input_dialog_ids, [&added_dialog_ids](auto input_dialog_id) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
CHECK(dialog_id.is_valid());
|
|
return !added_dialog_ids.insert(dialog_id).second;
|
|
});
|
|
};
|
|
remove_duplicates(new_filter->pinned_dialog_ids);
|
|
remove_duplicates(new_filter->included_dialog_ids);
|
|
remove_duplicates(new_filter->excluded_dialog_ids);
|
|
}
|
|
|
|
auto update_value = [](auto &new_value, const auto &old_server_value, const auto &new_server_value) {
|
|
// if the value was changed from other client and wasn't changed from the current client, update it
|
|
if (new_server_value != old_server_value && old_server_value == new_value) {
|
|
new_value = new_server_value;
|
|
}
|
|
};
|
|
|
|
update_value(new_filter->exclude_muted, old_server_filter->exclude_muted, new_server_filter->exclude_muted);
|
|
update_value(new_filter->exclude_read, old_server_filter->exclude_read, new_server_filter->exclude_read);
|
|
update_value(new_filter->exclude_archived, old_server_filter->exclude_archived, new_server_filter->exclude_archived);
|
|
update_value(new_filter->include_contacts, old_server_filter->include_contacts, new_server_filter->include_contacts);
|
|
update_value(new_filter->include_non_contacts, old_server_filter->include_non_contacts,
|
|
new_server_filter->include_non_contacts);
|
|
update_value(new_filter->include_bots, old_server_filter->include_bots, new_server_filter->include_bots);
|
|
update_value(new_filter->include_groups, old_server_filter->include_groups, new_server_filter->include_groups);
|
|
update_value(new_filter->include_channels, old_server_filter->include_channels, new_server_filter->include_channels);
|
|
|
|
if (new_filter->check_limits().is_error()) {
|
|
LOG(WARNING) << "Failed to merge local and remote changes in " << new_filter->dialog_filter_id
|
|
<< ", keep only local changes";
|
|
*new_filter = *old_filter;
|
|
}
|
|
|
|
update_value(new_filter->title, old_server_filter->title, new_server_filter->title);
|
|
update_value(new_filter->emoji, old_server_filter->emoji, new_server_filter->emoji);
|
|
return new_filter;
|
|
}
|
|
|
|
void DialogFilter::sort_input_dialog_ids(const Td *td, const char *source) {
|
|
if (!include_contacts && !include_non_contacts && !include_bots && !include_groups && !include_channels) {
|
|
excluded_dialog_ids.clear();
|
|
}
|
|
|
|
auto sort_input_dialog_ids = [contacts_manager =
|
|
td->contacts_manager_.get()](vector<InputDialogId> &input_dialog_ids) {
|
|
std::sort(input_dialog_ids.begin(), input_dialog_ids.end(),
|
|
[contacts_manager](InputDialogId lhs, InputDialogId rhs) {
|
|
auto get_order = [contacts_manager](InputDialogId input_dialog_id) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
if (dialog_id.get_type() != DialogType::SecretChat) {
|
|
return dialog_id.get() * 10;
|
|
}
|
|
auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
|
|
return DialogId(user_id).get() * 10 + 1;
|
|
};
|
|
return get_order(lhs) < get_order(rhs);
|
|
});
|
|
};
|
|
|
|
sort_input_dialog_ids(excluded_dialog_ids);
|
|
sort_input_dialog_ids(included_dialog_ids);
|
|
|
|
FlatHashSet<DialogId, DialogIdHash> all_dialog_ids;
|
|
for_each_dialog([&](const InputDialogId &input_dialog_id) {
|
|
auto dialog_id = input_dialog_id.get_dialog_id();
|
|
CHECK(dialog_id.is_valid());
|
|
LOG_CHECK(all_dialog_ids.insert(dialog_id).second) << source << ' ' << dialog_id << ' ' << *this;
|
|
});
|
|
}
|
|
|
|
vector<FolderId> DialogFilter::get_folder_ids() const {
|
|
if (exclude_archived && pinned_dialog_ids.empty() && included_dialog_ids.empty()) {
|
|
return {FolderId::main()};
|
|
}
|
|
return {FolderId::main(), FolderId::archive()};
|
|
}
|
|
|
|
bool DialogFilter::need_dialog(const Td *td, DialogId dialog_id, bool has_unread_mentions, bool is_muted,
|
|
bool has_unread_messages, FolderId folder_id) const {
|
|
if (InputDialogId::contains(pinned_dialog_ids, dialog_id)) {
|
|
return true;
|
|
}
|
|
if (InputDialogId::contains(included_dialog_ids, dialog_id)) {
|
|
return true;
|
|
}
|
|
if (InputDialogId::contains(excluded_dialog_ids, dialog_id)) {
|
|
return false;
|
|
}
|
|
if (dialog_id.get_type() == DialogType::SecretChat) {
|
|
auto user_id = td->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
|
|
if (user_id.is_valid()) {
|
|
auto user_dialog_id = DialogId(user_id);
|
|
if (InputDialogId::contains(pinned_dialog_ids, user_dialog_id)) {
|
|
return true;
|
|
}
|
|
if (InputDialogId::contains(included_dialog_ids, user_dialog_id)) {
|
|
return true;
|
|
}
|
|
if (InputDialogId::contains(excluded_dialog_ids, user_dialog_id)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!has_unread_mentions) {
|
|
if (exclude_muted && is_muted) {
|
|
return false;
|
|
}
|
|
if (exclude_read && !has_unread_messages) {
|
|
return false;
|
|
}
|
|
}
|
|
if (exclude_archived && folder_id == FolderId::archive()) {
|
|
return false;
|
|
}
|
|
switch (dialog_id.get_type()) {
|
|
case DialogType::User: {
|
|
auto user_id = dialog_id.get_user_id();
|
|
if (td->contacts_manager_->is_user_bot(user_id)) {
|
|
return include_bots;
|
|
}
|
|
if (user_id == td->contacts_manager_->get_my_id() || td->contacts_manager_->is_user_contact(user_id)) {
|
|
return include_contacts;
|
|
}
|
|
return include_non_contacts;
|
|
}
|
|
case DialogType::Chat:
|
|
return include_groups;
|
|
case DialogType::Channel:
|
|
return td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()) ? include_channels
|
|
: include_groups;
|
|
case DialogType::SecretChat: {
|
|
auto user_id = td->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
|
|
if (td->contacts_manager_->is_user_bot(user_id)) {
|
|
return include_bots;
|
|
}
|
|
if (td->contacts_manager_->is_user_contact(user_id)) {
|
|
return include_contacts;
|
|
}
|
|
return include_non_contacts;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
vector<DialogFilterId> DialogFilter::get_dialog_filter_ids(const vector<unique_ptr<DialogFilter>> &dialog_filters,
|
|
int32 main_dialog_list_position) {
|
|
auto result = transform(dialog_filters, [](const auto &dialog_filter) { return dialog_filter->dialog_filter_id; });
|
|
if (static_cast<size_t>(main_dialog_list_position) <= result.size()) {
|
|
result.insert(result.begin() + main_dialog_list_position, DialogFilterId());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DialogFilter::set_dialog_filters_order(vector<unique_ptr<DialogFilter>> &dialog_filters,
|
|
vector<DialogFilterId> dialog_filter_ids) {
|
|
auto old_dialog_filter_ids = get_dialog_filter_ids(dialog_filters, -1);
|
|
if (old_dialog_filter_ids == dialog_filter_ids) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "Reorder chat filters from " << old_dialog_filter_ids << " to " << dialog_filter_ids;
|
|
|
|
if (dialog_filter_ids.size() != old_dialog_filter_ids.size()) {
|
|
for (auto dialog_filter_id : old_dialog_filter_ids) {
|
|
if (!td::contains(dialog_filter_ids, dialog_filter_id)) {
|
|
dialog_filter_ids.push_back(dialog_filter_id);
|
|
}
|
|
}
|
|
CHECK(dialog_filter_ids.size() == old_dialog_filter_ids.size());
|
|
}
|
|
if (old_dialog_filter_ids == dialog_filter_ids) {
|
|
return false;
|
|
}
|
|
|
|
CHECK(dialog_filter_ids.size() == dialog_filters.size());
|
|
for (size_t i = 0; i < dialog_filters.size(); i++) {
|
|
for (size_t j = i; j < dialog_filters.size(); j++) {
|
|
if (dialog_filters[j]->dialog_filter_id == dialog_filter_ids[i]) {
|
|
if (i != j) {
|
|
std::swap(dialog_filters[i], dialog_filters[j]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
CHECK(dialog_filters[i]->dialog_filter_id == dialog_filter_ids[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DialogFilter::are_similar(const DialogFilter &lhs, const DialogFilter &rhs) {
|
|
if (lhs.title == rhs.title) {
|
|
return true;
|
|
}
|
|
if (!are_flags_equal(lhs, rhs)) {
|
|
return false;
|
|
}
|
|
|
|
vector<InputDialogId> empty_input_dialog_ids;
|
|
if (InputDialogId::are_equivalent(lhs.excluded_dialog_ids, empty_input_dialog_ids) !=
|
|
InputDialogId::are_equivalent(rhs.excluded_dialog_ids, empty_input_dialog_ids)) {
|
|
return false;
|
|
}
|
|
if ((InputDialogId::are_equivalent(lhs.pinned_dialog_ids, empty_input_dialog_ids) &&
|
|
InputDialogId::are_equivalent(lhs.included_dialog_ids, empty_input_dialog_ids)) !=
|
|
(InputDialogId::are_equivalent(rhs.pinned_dialog_ids, empty_input_dialog_ids) &&
|
|
InputDialogId::are_equivalent(rhs.included_dialog_ids, empty_input_dialog_ids))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DialogFilter::are_equivalent(const DialogFilter &lhs, const DialogFilter &rhs) {
|
|
return lhs.title == rhs.title && lhs.emoji == rhs.emoji &&
|
|
InputDialogId::are_equivalent(lhs.pinned_dialog_ids, rhs.pinned_dialog_ids) &&
|
|
InputDialogId::are_equivalent(lhs.included_dialog_ids, rhs.included_dialog_ids) &&
|
|
InputDialogId::are_equivalent(lhs.excluded_dialog_ids, rhs.excluded_dialog_ids) && are_flags_equal(lhs, rhs);
|
|
}
|
|
|
|
StringBuilder &operator<<(StringBuilder &string_builder, const DialogFilter &filter) {
|
|
return string_builder << filter.dialog_filter_id << " (pinned " << filter.pinned_dialog_ids << ", included "
|
|
<< filter.included_dialog_ids << ", excluded " << filter.excluded_dialog_ids << ", "
|
|
<< filter.exclude_muted << ' ' << filter.exclude_read << ' ' << filter.exclude_archived << '/'
|
|
<< filter.include_contacts << ' ' << filter.include_non_contacts << ' ' << filter.include_bots
|
|
<< ' ' << filter.include_groups << ' ' << filter.include_channels << ')';
|
|
}
|
|
|
|
void DialogFilter::init_icon_names() {
|
|
static bool is_inited = [&] {
|
|
vector<string> emojis{"\xF0\x9F\x92\xAC", "\xE2\x9C\x85", "\xF0\x9F\x94\x94",
|
|
"\xF0\x9F\xA4\x96", "\xF0\x9F\x93\xA2", "\xF0\x9F\x91\xA5",
|
|
"\xF0\x9F\x91\xA4", "\xF0\x9F\x93\x81", "\xF0\x9F\x93\x8B",
|
|
"\xF0\x9F\x90\xB1", "\xF0\x9F\x91\x91", "\xE2\xAD\x90\xEF\xB8\x8F",
|
|
"\xF0\x9F\x8C\xB9", "\xF0\x9F\x8E\xAE", "\xF0\x9F\x8F\xA0",
|
|
"\xE2\x9D\xA4\xEF\xB8\x8F", "\xF0\x9F\x8E\xAD", "\xF0\x9F\x8D\xB8",
|
|
"\xE2\x9A\xBD\xEF\xB8\x8F", "\xF0\x9F\x8E\x93", "\xF0\x9F\x93\x88",
|
|
"\xE2\x9C\x88\xEF\xB8\x8F", "\xF0\x9F\x92\xBC", "\xF0\x9F\x9B\xAB",
|
|
"\xF0\x9F\x93\x95", "\xF0\x9F\x92\xA1", "\xF0\x9F\x91\x8D",
|
|
"\xF0\x9F\x92\xB0", "\xF0\x9F\x8E\xB5", "\xF0\x9F\x8E\xA8"};
|
|
vector<string> icon_names{"All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom",
|
|
"Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love",
|
|
"Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work", "Airplane",
|
|
"Book", "Light", "Like", "Money", "Note", "Palette"};
|
|
|
|
CHECK(emojis.size() == icon_names.size());
|
|
for (size_t i = 0; i < emojis.size(); i++) {
|
|
remove_emoji_modifiers_in_place(emojis[i]);
|
|
bool is_inserted = emoji_to_icon_name_.emplace(emojis[i], icon_names[i]).second &&
|
|
icon_name_to_emoji_.emplace(icon_names[i], emojis[i]).second;
|
|
CHECK(is_inserted);
|
|
}
|
|
return true;
|
|
}();
|
|
CHECK(is_inited);
|
|
}
|
|
|
|
bool DialogFilter::are_flags_equal(const DialogFilter &lhs, const DialogFilter &rhs) {
|
|
return lhs.exclude_muted == rhs.exclude_muted && lhs.exclude_read == rhs.exclude_read &&
|
|
lhs.exclude_archived == rhs.exclude_archived && lhs.include_contacts == rhs.include_contacts &&
|
|
lhs.include_non_contacts == rhs.include_non_contacts && lhs.include_bots == rhs.include_bots &&
|
|
lhs.include_groups == rhs.include_groups && lhs.include_channels == rhs.include_channels;
|
|
}
|
|
|
|
FlatHashMap<string, string> DialogFilter::emoji_to_icon_name_;
|
|
FlatHashMap<string, string> DialogFilter::icon_name_to_emoji_;
|
|
|
|
} // namespace td
|