diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 0cd454ff5..77a5bee78 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -3709,6 +3709,9 @@ setChatChatList chat_id:int53 chat_list:ChatList = Ok; //@description Returns information about a chat filter by its identifier @chat_filter_id Chat filter identifier getChatFilter chat_filter_id:int32 = ChatFilter; +//@description Creates new chat filter @filter The chat filter +createChatFilter filter:chatFilter = Ok; + //@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires can_change_info rights. The title will not be changed until the request to the server has been completed //@chat_id Chat identifier @title New title of the chat; 1-128 characters diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 7998b8d48..84ae4cd9f 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index a2cd48f71..e5c5377bc 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -143,11 +143,11 @@ void AuthManager::check_bot_token(uint64 query_id, string bot_token) { } if (state_ != State::WaitPhoneNumber && state_ != State::Ok) { // TODO do not allow State::Ok - return on_query_error(query_id, Status::Error(8, "Call to checkAuthenticationBotToken unexpected")); + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationBotToken unexpected")); } if (!send_code_helper_.phone_number().empty() || was_qr_code_request_) { return on_query_error( - query_id, Status::Error(8, "Cannot set bot token after authentication beginning. You need to log out first")); + query_id, Status::Error(400, "Cannot set bot token after authentication beginning. You need to log out first")); } if (was_check_bot_token_ && bot_token_ != bot_token) { return on_query_error(query_id, Status::Error(8, "Cannot change bot token. You need to log out first")); diff --git a/td/telegram/DialogFilterId.h b/td/telegram/DialogFilterId.h index f7155b8ae..ba6dc7e5b 100644 --- a/td/telegram/DialogFilterId.h +++ b/td/telegram/DialogFilterId.h @@ -20,12 +20,20 @@ class DialogFilterId { public: DialogFilterId() = default; - explicit DialogFilterId(int32 dialog_filter_id) : id(dialog_filter_id) { + explicit constexpr DialogFilterId(int32 dialog_filter_id) : id(dialog_filter_id) { } template ::value>> DialogFilterId(T dialog_filter_id) = delete; + static constexpr DialogFilterId min() { + return DialogFilterId(static_cast(2)); + } + static constexpr DialogFilterId max() { + return DialogFilterId(static_cast(255)); + } + bool is_valid() const { + // don't check min() and max() for greater future flexibility return id > 0; } diff --git a/td/telegram/InputDialogId.cpp b/td/telegram/InputDialogId.cpp index 93b7ec15d..cb76c0bb0 100644 --- a/td/telegram/InputDialogId.cpp +++ b/td/telegram/InputDialogId.cpp @@ -48,10 +48,11 @@ InputDialogId::InputDialogId(const tl_object_ptr &input LOG(ERROR) << "Receive " << to_string(input_peer); } -vector InputDialogId::get_input_dialog_ids(const vector> &input_peers) { +vector InputDialogId::get_input_dialog_ids( + const vector> &input_peers) { vector result; result.reserve(input_peers.size()); - for (auto &input_peer:input_peers) { + for (auto &input_peer : input_peers) { InputDialogId input_dialog_id(input_peer); if (input_dialog_id.is_valid()) { result.push_back(input_dialog_id); @@ -60,6 +61,32 @@ vector InputDialogId::get_input_dialog_ids(const vector> InputDialogId::get_input_dialog_peers( + const vector &input_dialog_ids) { + vector> result; + result.reserve(input_dialog_ids.size()); + for (auto input_dialog_id : input_dialog_ids) { + auto input_peer = input_dialog_id.get_input_peer(); + if (input_peer != nullptr) { + result.push_back(telegram_api::make_object(std::move(input_peer))); + } + } + return result; +} + +vector> InputDialogId::get_input_peers( + const vector &input_dialog_ids) { + vector> result; + result.reserve(input_dialog_ids.size()); + for (auto input_dialog_id : input_dialog_ids) { + auto input_peer = input_dialog_id.get_input_peer(); + if (input_peer != nullptr) { + result.push_back(std::move(input_peer)); + } + } + return result; +} + tl_object_ptr InputDialogId::get_input_peer() const { switch (dialog_id.get_type()) { case DialogType::User: diff --git a/td/telegram/InputDialogId.h b/td/telegram/InputDialogId.h index 6069bc404..d027550a2 100644 --- a/td/telegram/InputDialogId.h +++ b/td/telegram/InputDialogId.h @@ -21,14 +21,19 @@ class InputDialogId { public: InputDialogId() = default; - explicit InputDialogId(DialogId dialog_id): dialog_id(dialog_id) { - CHECK(dialog_id.get_type() == DialogType::SecretChat); + explicit InputDialogId(DialogId dialog_id) : dialog_id(dialog_id) { } explicit InputDialogId(const tl_object_ptr &input_peer); static vector get_input_dialog_ids(const vector> &input_peers); + static vector> get_input_dialog_peers( + const vector &input_dialog_ids); + + static vector> get_input_peers( + const vector &input_dialog_ids); + bool operator==(const InputDialogId &other) const { return dialog_id == other.dialog_id && access_hash == other.access_hash; } diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 0d7f1e055..f738193f1 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -218,13 +218,10 @@ class GetDialogsQuery : public Td::ResultHandler { void send(vector input_dialog_ids) { LOG(INFO) << "Send GetDialogsQuery to get " << input_dialog_ids; + CHECK(!input_dialog_ids.empty()); CHECK(input_dialog_ids.size() <= 100); - auto input_dialog_peers = transform( - input_dialog_ids, [](InputDialogId input_dialog_id) -> telegram_api::object_ptr { - auto input_peer = input_dialog_id.get_input_peer(); - CHECK(input_peer != nullptr); - return telegram_api::make_object(std::move(input_peer)); - }); + 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)))); } @@ -428,6 +425,37 @@ class GetScheduledMessagesQuery : public Td::ResultHandler { } }; +class UpdateDialogFilterQuery : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdateDialogFilterQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogFilterId dialog_filter_id, tl_object_ptr 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(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + LOG(INFO) << "Receive result for UpdateDialogFilterQuery: " << result_ptr.ok(); + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + promise_.set_error(std::move(status)); + } +}; + class UpdateDialogPinnedMessageQuery : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -4775,6 +4803,40 @@ struct MessagesManager::DialogFilter { template void parse(ParserT &parser); + telegram_api::object_ptr get_input_dialog_filter() const { + int32 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( + 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)); + } + friend bool operator==(const DialogFilter &lhs, const DialogFilter &rhs) { return lhs.dialog_filter_id == rhs.dialog_filter_id && lhs.title == rhs.title && lhs.emoji == rhs.emoji && lhs.pinned_dialog_ids == rhs.pinned_dialog_ids && lhs.included_dialog_ids == rhs.included_dialog_ids && @@ -13518,7 +13580,7 @@ void MessagesManager::load_dialogs(vector dialog_ids, Promise && bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise &&promise) { if (!dialog_id.is_valid()) { - promise.set_error(Status::Error(6, "Invalid chat identifier")); + promise.set_error(Status::Error(6, "Invalid chat identifier specified")); return false; } @@ -13578,6 +13640,10 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise &&promise) { + if (!dialog_filter_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid chat filter identifier specified")); + } + auto filter = get_dialog_filter(dialog_filter_id); if (filter == nullptr) { return promise.set_value(Unit()); @@ -13968,6 +14034,8 @@ void MessagesManager::on_get_dialog_filters(Resultinclude_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; + + // TODO add secret chats to the filter dialog_filters.push_back(std::move(dialog_filter)); } @@ -14896,6 +14964,122 @@ td_api::object_ptr MessagesManager::get_message_link_in return td_api::make_object(is_public, dialog_id.get(), std::move(message), for_album); } +Result> MessagesManager::create_dialog_filter( + td_api::object_ptr filter) { + CHECK(filter != nullptr); + for (auto chat_ids : {&filter->pinned_chat_ids_, &filter->excluded_chat_ids_, &filter->included_chat_ids_}) { + for (auto chat_id : *chat_ids) { + DialogId dialog_id(chat_id); + if (!dialog_id.is_valid()) { + return Status::Error(400, "Invalid chat identifier specified"); + } + const Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return Status::Error(400, "Chat not found"); + } + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return Status::Error(6, "Can't access the chat"); + } + if (d->order == DEFAULT_ORDER) { + return Status::Error(400, "Chat is not in the chat list"); + } + } + } + + DialogFilterId dialog_filter_id; + do { + auto min_id = static_cast(DialogFilterId::min().get()); + auto max_id = static_cast(DialogFilterId::max().get()); + dialog_filter_id = DialogFilterId(static_cast(Random::fast(min_id, max_id))); + } while (get_dialog_filter(dialog_filter_id) != nullptr); + + auto dialog_filter = make_unique(); + dialog_filter->dialog_filter_id = dialog_filter_id; + + std::unordered_set added_dialog_ids; + auto add_chats = [this, &added_dialog_ids](vector &input_dialog_ids, const vector &chat_ids) { + for (auto &chat_id : chat_ids) { + if (!added_dialog_ids.insert(chat_id).second) { + // do not allow duplicate chat_ids + continue; + } + + auto dialog_id = DialogId(chat_id); + auto input_peer = get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr || input_peer->get_id() == telegram_api::inputPeerSelf::ID) { + input_dialog_ids.push_back(InputDialogId(dialog_id)); + } else { + input_dialog_ids.push_back(InputDialogId(input_peer)); + } + } + }; + 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_); + + if (dialog_filter->excluded_dialog_ids.size() > MAX_INCLUDED_FILTER_DIALOGS) { + return Status::Error(400, "Too much excluded chats"); + } + if (dialog_filter->included_dialog_ids.size() > MAX_INCLUDED_FILTER_DIALOGS) { + return Status::Error(400, "Too much included chats"); + } + if (dialog_filter->pinned_dialog_ids.size() + dialog_filter->included_dialog_ids.size() > + MAX_INCLUDED_FILTER_DIALOGS) { + return Status::Error(400, "Too much pinned chats"); + } + + dialog_filter->title = std::move(filter->title_); + dialog_filter->emoji = std::move(filter->emoji_); + 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_; + + return std::move(dialog_filter); +} + +void MessagesManager::create_dialog_filter(td_api::object_ptr filter, Promise &&promise) { + if (dialog_filters_.size() >= MAX_DIALOG_FILTERS) { + return promise.set_error(Status::Error(400, "Maximum number of chat folders exceeded")); + } + + auto r_dialog_filter = create_dialog_filter(std::move(filter)); + if (r_dialog_filter.is_error()) { + return promise.set_error(r_dialog_filter.move_as_error()); + } + auto dialog_filter = r_dialog_filter.move_as_ok(); + CHECK(dialog_filter != nullptr); + auto dialog_filter_id = dialog_filter->dialog_filter_id; + auto input_dialog_filter = dialog_filter->get_input_dialog_filter(); + + // TODO logevent + // TODO add dialog filter locally + // TODO SequenceDispatcher + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter = std::move(dialog_filter), + promise = std::move(promise)](Result result) mutable { + send_closure(actor_id, &MessagesManager::on_create_dialog_filter, std::move(dialog_filter), + result.is_error() ? result.move_as_error() : Status::OK(), std::move(promise)); + }); + td_->create_handler(std::move(query_promise)) + ->send(dialog_filter_id, std::move(input_dialog_filter)); +} + +void MessagesManager::on_create_dialog_filter(unique_ptr dialog_filter, Status result, + Promise &&promise) { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + + // TODO update all changed chat lists and their unread counts + dialog_filters_.push_back(std::move(dialog_filter)); + send_update_chat_filters(false); + promise.set_value(Unit()); +} + Status MessagesManager::delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) { if (td_->auth_manager_->is_bot()) { return Status::Error(6, "Bots can't delete chat reply markup"); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 38a3ae9fa..1ca00cbf6 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -551,6 +551,8 @@ class MessagesManager : public Actor { td_api::object_ptr get_message_link_info_object(const MessageLinkInfo &info) const; + void create_dialog_filter(td_api::object_ptr filter, Promise &&promise); + Status delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) TD_WARN_UNUSED_RESULT; Status set_dialog_draft_message(DialogId dialog_id, @@ -1507,6 +1509,8 @@ class MessagesManager : public Actor { static constexpr int64 SPONSORED_DIALOG_ORDER = static_cast(2147483647) << 32; static constexpr int32 MIN_PINNED_DIALOG_DATE = 2147000000; // some big date static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit + static constexpr int32 MAX_DIALOG_FILTERS = 10; // server side limit + static constexpr int32 MAX_INCLUDED_FILTER_DIALOGS = 100; // server side limit static constexpr int32 DIALOG_FILTERS_CACHE_TIME = 86400; static constexpr int32 UPDATE_CHANNEL_TO_LONG_FLAG_HAS_PTS = 1 << 0; @@ -2200,6 +2204,10 @@ class MessagesManager : public Actor { void update_dialogs_hints(const Dialog *d); void update_dialogs_hints_rating(const Dialog *d); + Result> create_dialog_filter(td_api::object_ptr filter); + + void on_create_dialog_filter(unique_ptr dialog_filter, Status result, Promise &&promise); + DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id); const DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id) const; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 0f5fc0636..b216cca34 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5877,6 +5877,17 @@ void Td::on_request(uint64 id, const td_api::getChatFilter &request) { CREATE_REQUEST(GetChatFilterRequest, request.chat_filter_id_); } +void Td::on_request(uint64 id, td_api::createChatFilter &request) { + CHECK_IS_USER(); + if (request.filter_ == nullptr) { + return send_error_raw(id, 400, "Chat filter must be non-empty"); + } + CLEAN_INPUT_STRING(request.filter_->title_); + CLEAN_INPUT_STRING(request.filter_->emoji_); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->create_dialog_filter(std::move(request.filter_), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setChatTitle &request) { CLEAN_INPUT_STRING(request.title_); CREATE_OK_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 12b27b602..a6a313ffb 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -677,6 +677,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getChatFilter &request); + void on_request(uint64 id, td_api::createChatFilter &request); + void on_request(uint64 id, td_api::setChatTitle &request); void on_request(uint64 id, const td_api::setChatPhoto &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 67921613f..80c141bb1 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -1079,6 +1079,25 @@ class CliClient final : public Actor { return nullptr; } + td_api::object_ptr as_chat_filter(string filter) const { + string title; + string pinned_chat_ids; + string included_chat_ids; + string excluded_chat_ids; + std::tie(title, filter) = split(filter); + std::tie(pinned_chat_ids, filter) = split(filter); + std::tie(included_chat_ids, filter) = split(filter); + std::tie(excluded_chat_ids, filter) = split(filter); + + auto rand_bool = [] { + return Random::fast(0, 1) == 1; + }; + + return td_api::make_object( + title, string(), as_chat_ids(pinned_chat_ids), as_chat_ids(included_chat_ids), as_chat_ids(excluded_chat_ids), + rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool()); + } + static td_api::object_ptr get_top_chat_category(MutableSlice category) { category = trim(category); to_lower_inplace(category); @@ -3487,6 +3506,8 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_chat_id(chat_id), as_chat_list(op))); } else if (op == "gcf") { send_request(td_api::make_object(as_chat_filter_id(args))); + } else if (op == "ccf") { + send_request(td_api::make_object(as_chat_filter(args))); } else if (op == "sct") { string chat_id; string title;