// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026 // // 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) // #pragma once #include "td/telegram/DialogId.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesInfo.h" #include "td/telegram/OrderedMessage.h" #include "td/telegram/SavedMessagesTopicId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/actor/actor.h" #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/Promise.h" #include "td/utils/Status.h" #include #include namespace td { class DraftMessage; class Td; class SavedMessagesManager final : public Actor { public: SavedMessagesManager(Td *td, ActorShared<> parent); bool have_topic(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id) const; SavedMessagesTopicId get_topic_id(DialogId dialog_id, int64 topic_id) const; vector get_topic_ids(DialogId dialog_id, const vector &topic_ids) const; int64 get_saved_messages_topic_id_object(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id); bool is_last_topic_message(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId message_id) const; void on_topic_message_added(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId message_id, int32 message_date, const bool from_update, const bool need_update, const bool is_new, const char *source); void on_topic_message_updated(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId message_id); void on_topic_message_deleted(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId message_id, bool only_from_memory, const char *source); void on_all_dialog_messages_deleted(DialogId dialog_id); void on_topic_draft_message_updated(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 draft_message_date); void clear_monoforum_topic_draft_by_sent_message(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, bool message_clear_draft, MessageContentType message_content_type); void read_monoforum_topic_messages(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId read_inbox_max_message_id); void on_update_read_monoforum_inbox(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId read_inbox_max_message_id); void on_update_read_all_monoforum_inbox(DialogId dialog_id, MessageId read_inbox_max_message_id); void on_update_read_monoforum_outbox(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId read_outbox_max_message_id); void on_update_monoforum_nopaid_messages_exception(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, bool nopaid_messages_exception); void on_update_topic_draft_message(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, telegram_api::object_ptr &&draft_message, int32 try_count = 0); void on_update_topic_is_marked_as_unread(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, bool is_marked_as_unread); void on_topic_reaction_count_changed(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 count, bool is_relative); void repair_topic_unread_reaction_count(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id); void load_saved_messages_topics(int32 limit, Promise &&promise); void load_monoforum_topics(DialogId dialog_id, int32 limit, Promise &&promise); void on_get_saved_messages_topics(DialogId dialog_id, uint32 generation, SavedMessagesTopicId expected_saved_messages_topic_id, bool is_pinned, int32 limit, telegram_api::object_ptr &&saved_dialogs_ptr, Promise &&promise); void get_monoforum_topic(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise> &&promise); void reload_monoforum_topic(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise> &&promise); void get_monoforum_topic_history(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId from_message_id, int32 offset, int32 limit, Promise> &&promise); void get_saved_messages_topic_history(SavedMessagesTopicId saved_messages_topic_id, MessageId from_message_id, int32 offset, int32 limit, Promise> &&promise); void delete_monoforum_topic_history(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise &&promise); void delete_saved_messages_topic_history(SavedMessagesTopicId saved_messages_topic_id, Promise &&promise); void get_monoforum_topic_message_by_date(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 date, Promise> &&promise); void get_saved_messages_topic_message_by_date(SavedMessagesTopicId saved_messages_topic_id, int32 date, Promise> &&promise); void delete_monoforum_topic_messages_by_date(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 min_date, int32 max_date, Promise &&promise); void delete_saved_messages_topic_messages_by_date(SavedMessagesTopicId saved_messages_topic_id, int32 min_date, int32 max_date, Promise &&promise); void toggle_saved_messages_topic_is_pinned(SavedMessagesTopicId saved_messages_topic_id, bool is_pinned, Promise &&promise); void set_pinned_saved_messages_topics(vector saved_messages_topic_ids, Promise &&promise); void reload_pinned_saved_messages_topics(); void set_monoforum_topic_is_marked_as_unread(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, bool is_marked_as_unread, Promise &&promise); Status set_monoforum_topic_draft_message(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, unique_ptr &&draft_message); void unpin_all_monoforum_topic_messages(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise &&promise); void read_all_monoforum_topic_reactions(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise &&promise); void get_monoforum_topic_revenue(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise> &&promise); void toggle_monoforum_topic_nopaid_messages_exception(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, bool nopaid_messages_exception, bool refund_payments, Promise &&promise); void get_monoforum_message_author(MessageFullId message_full_id, Promise> &&promise); void get_current_state(vector> &updates) const; private: static constexpr int32 MAX_GET_HISTORY = 100; // server-side limit static constexpr int64 MIN_PINNED_TOPIC_ORDER = static_cast(2147000000) << 32; struct SavedMessagesTopicInfo { DialogId peer_dialog_id_; MessageId last_topic_message_id_; unique_ptr draft_message_; MessageId read_inbox_max_message_id_; MessageId read_outbox_max_message_id_; int32 unread_count_ = 0; int32 unread_reaction_count_ = 0; bool is_marked_as_unread_ = false; bool nopaid_messages_exception_ = false; bool is_pinned_ = false; }; struct SavedMessagesTopic { DialogId dialog_id_; SavedMessagesTopicId saved_messages_topic_id_; OrderedMessages ordered_messages_; MessageId last_message_id_; MessageId read_inbox_max_message_id_; MessageId read_outbox_max_message_id_; unique_ptr draft_message_; int32 local_message_count_ = 0; int32 server_message_count_ = 0; int32 sent_message_count_ = -1; int32 unread_count_ = 0; int32 unread_reaction_count_ = 0; int32 last_message_date_ = 0; int32 draft_message_date_ = 0; int64 pinned_order_ = 0; int64 private_order_ = 0; bool is_server_message_count_inited_ = false; bool is_marked_as_unread_ = false; bool nopaid_messages_exception_ = false; bool is_received_from_server_ = false; bool need_repair_unread_count_ = false; bool is_changed_ = false; }; class TopicDate { int64 order_; SavedMessagesTopicId topic_id_; public: TopicDate(int64 order, SavedMessagesTopicId topic_id) : order_(order), topic_id_(topic_id) { } bool operator<(const TopicDate &other) const { return order_ > other.order_ || (order_ == other.order_ && topic_id_.get_unique_id() > other.topic_id_.get_unique_id()); } bool operator<=(const TopicDate &other) const { return order_ > other.order_ || (order_ == other.order_ && topic_id_.get_unique_id() >= other.topic_id_.get_unique_id()); } bool operator==(const TopicDate &other) const { return order_ == other.order_ && topic_id_ == other.topic_id_; } bool operator!=(const TopicDate &other) const { return !(*this == other); } SavedMessagesTopicId get_topic_id() const { return topic_id_; } }; static const TopicDate MIN_TOPIC_DATE; static const TopicDate MAX_TOPIC_DATE; struct TopicList { DialogId dialog_id_; int32 server_total_count_ = -1; int32 sent_total_count_ = -1; uint32 generation_ = 0; vector pinned_saved_messages_topic_ids_; bool are_pinned_saved_messages_topics_inited_ = false; std::set ordered_topics_; TopicDate last_topic_date_ = MIN_TOPIC_DATE; // in memory vector> load_pinned_queries_; vector> load_queries_; int32 offset_date_ = std::numeric_limits::max(); DialogId offset_dialog_id_; MessageId offset_message_id_; FlatHashMap, SavedMessagesTopicIdHash> topics_; FlatHashMap>>, SavedMessagesTopicIdHash> get_topic_queries_; }; void tear_down() final; SavedMessagesTopic *get_topic(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id); const SavedMessagesTopic *get_topic(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id) const; static SavedMessagesTopic *get_topic(TopicList *topic_list, SavedMessagesTopicId saved_messages_topic_id); static const SavedMessagesTopic *get_topic(const TopicList *topic_list, SavedMessagesTopicId saved_messages_topic_id); SavedMessagesTopic *add_topic(TopicList *topic_list, SavedMessagesTopicId saved_messages_topic_id, bool from_server); void get_pinned_saved_dialogs(int32 limit, Promise &&promise); void on_get_pinned_saved_dialogs(Result &&result); void get_saved_dialogs(TopicList *topic_list, int32 limit, Promise &&promise); static SavedMessagesTopicInfo get_saved_messages_topic_info( Td *td, telegram_api::object_ptr &&dialog_ptr, bool is_saved_messages); void process_saved_messages_topics(DialogId dialog_id, uint32 generation, SavedMessagesTopicId expected_saved_messages_topic_id, bool is_pinned, int32 limit, int32 total_count, vector> &&dialogs, vector> &&messages, bool is_last, Promise &&promise); void on_get_saved_dialogs(TopicList *topic_list, Result &&result); void on_get_monoforum_topic(DialogId dialog_id, uint32 generation, SavedMessagesTopicId saved_messages_topic_id, Result &&result); void on_get_topic_history(DialogId dialog_id, uint32 generation, SavedMessagesTopicId saved_messages_topic_id, MessageId from_message_id, int32 offset, int32 limit, int32 left_tries, Result &&r_info, Promise> &&promise); void repair_topic_unread_count(const SavedMessagesTopic *topic); void read_topic_messages(SavedMessagesTopic *topic, MessageId read_inbox_max_message_id, int32 hint_unread_count); void do_set_topic_last_message_id(SavedMessagesTopic *topic, MessageId last_message_id, int32 last_message_date); void do_set_topic_read_inbox_max_message_id(SavedMessagesTopic *topic, MessageId read_inbox_max_message_id, int32 unread_count, const char *source); void do_set_topic_read_outbox_max_message_id(SavedMessagesTopic *topic, MessageId read_outbox_max_message_id); void do_set_topic_is_marked_as_unread(SavedMessagesTopic *topic, bool is_marked_as_unread); void do_set_topic_nopaid_messages_exception(SavedMessagesTopic *topic, bool nopaid_messages_exception); void do_set_topic_unread_reaction_count(SavedMessagesTopic *topic, int32 unread_reaction_count); void do_set_topic_draft_message(SavedMessagesTopic *topic, unique_ptr &&draft_message, bool from_update, bool need_delete_files); void load_topics(TopicList *topic_list, int32 limit, Promise &&promise); int64 get_next_pinned_saved_messages_topic_order(); bool set_pinned_saved_messages_topics(vector added_saved_messages_topic_ids); bool set_saved_messages_topic_is_pinned(SavedMessagesTopicId saved_messages_topic_id, bool is_pinned, const char *source); bool set_saved_messages_topic_is_pinned(SavedMessagesTopic *topic, bool is_pinned, const char *source); int32 get_pinned_saved_messages_topic_limit() const; int64 get_topic_order(int32 message_date, MessageId message_id); static int64 get_topic_public_order(const TopicList *topic_list, const SavedMessagesTopic *topic); void set_last_topic_date(TopicList *topic_list, TopicDate topic_date); void on_topic_changed(TopicList *topic_list, SavedMessagesTopic *topic, const char *source); void on_topic_message_count_changed(const SavedMessagesTopic *topic, const char *source); void update_topic_message_count(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id); void get_topic_history(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId from_message_id, int32 offset, int32 limit, int32 left_tries, Promise> &&promise); void do_get_topic_history(const TopicList *topic_list, const SavedMessagesTopic *topic, DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageId from_message_id, int32 offset, int32 limit, int32 left_tries, Promise> &&promise); void get_topic_message_by_date(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 date, Promise> &&promise); void delete_topic_history(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, Promise &&promise); void delete_topic_messages_by_date(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, int32 min_date, int32 max_date, Promise &&promise); td_api::object_ptr get_saved_messages_topic_object(const SavedMessagesTopic *topic) const; td_api::object_ptr get_update_saved_messages_topic_object( const SavedMessagesTopic *topic) const; void send_update_saved_messages_topic(const TopicList *topic_list, const SavedMessagesTopic *topic, const char *source) const; td_api::object_ptr get_direct_messages_chat_topic_object( const TopicList *topic_list, const SavedMessagesTopic *topic) const; td_api::object_ptr get_update_direct_messages_chat_topic_object( const TopicList *topic_list, const SavedMessagesTopic *topic) const; td_api::object_ptr get_update_saved_messages_topic_count_object() const; void update_saved_messages_topic_sent_total_count(TopicList *topic_list, const char *source); td_api::object_ptr get_update_topic_message_count_object( const SavedMessagesTopic *topic) const; Status check_monoforum_dialog_id(DialogId dialog_id) const; Result get_monoforum_topic_list(DialogId dialog_id); TopicList *get_topic_list(DialogId dialog_id); const TopicList *get_topic_list(DialogId dialog_id) const; TopicList *add_topic_list(DialogId dialog_id); Td *td_; ActorShared<> parent_; int64 current_pinned_saved_messages_topic_order_ = MIN_PINNED_TOPIC_ORDER; TopicList topic_list_; FlatHashMap, DialogIdHash> monoforum_topic_lists_; uint32 current_topic_list_generation_ = 0; }; } // namespace td