diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 9f2edef06..ad7652f99 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -533,6 +533,7 @@ chatTypeSecret secret_chat_id:int32 user_id:int32 = ChatType; //@last_message Last message in the chat; may be null //@order Descending parameter by which chats are sorted in the main chat list. If the order number of two chats is the same, they must be sorted in descending order by ID. If 0, the position of the chat in the list is undetermined //@is_pinned True, if the chat is pinned +//@is_marked_as_unread True, if the chat is marked as unread //@is_sponsored True, if the chat is sponsored by enabled proxy //@can_be_reported True, if the chat can be reported to Telegram moderators through reportChat //@default_disable_notification Default value of disable_notification parameter, used when a message is sent to the chat @@ -544,7 +545,7 @@ chatTypeSecret secret_chat_id:int32 user_id:int32 = ChatType; //@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat //@draft_message A draft of a message in the chat; may be null //@client_data Contains client-specific data associated with the chat. (For example, the chat position or local chat notification settings can be stored here.) Persistent if a message database is used -chat id:int53 type:ChatType title:string photo:chatPhoto last_message:message order:int64 is_pinned:Bool is_sponsored:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:chatNotificationSettings reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; +chat id:int53 type:ChatType title:string photo:chatPhoto last_message:message order:int64 is_pinned:Bool is_marked_as_unread:Bool is_sponsored:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:chatNotificationSettings reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; //@description Represents a list of chats @chat_ids List of chat identifiers chats chat_ids:vector = Chats; @@ -2073,6 +2074,9 @@ updateChatOrder chat_id:int53 order:int64 = Update; //@description A chat was pinned or unpinned @chat_id Chat identifier @is_pinned New value of is_pinned @order New value of the chat order updateChatIsPinned chat_id:int53 is_pinned:Bool order:int64 = Update; +//@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread +updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update; + //@description A chat is_sponsored field has changed @chat_id Chat identifier @is_sponsored New value of is_sponsored @order New value of chat order updateChatIsSponsored chat_id:int53 is_sponsored:Bool order:int64 = Update; @@ -2659,6 +2663,9 @@ setChatNotificationSettings chat_id:int53 notification_settings:chatNotification //@description Changes the pinned state of a chat. You can pin up to GetOption("pinned_chat_count_max") non-secret chats and the same number of secret chats @chat_id Chat identifier @is_pinned New value of is_pinned toggleChatIsPinned chat_id:int53 is_pinned:Bool = Ok; +//@description Changes the marked as unread state of a chat @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread +toggleChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Ok; + //@description Changes the value of default disable_notification parameter, used when a message is sent to a chat @chat_id Chat identifier @default_disable_notification New value of default_disable_notification toggleChatDefaultDisableNotification chat_id:int53 default_disable_notification:Bool = Ok; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 55d5c9c00..8bd0d22d8 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 c34b6bf95..d43f7a1f5 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -864,6 +864,9 @@ void AuthManager::on_authorization(tl_object_ptrupdates_manager_->get_difference("on_authorization"); td->on_online_updated(true, true); td->schedule_get_terms_of_service(0); + if (!is_bot()) { + G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); + } send_closure(G()->config_manager(), &ConfigManager::request_config); if (query_id_ != 0) { on_query_ok(); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index e214f0e69..a4211c51c 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -234,6 +234,32 @@ class GetDialogsQuery : public Td::ResultHandler { } }; */ +class GetDialogUnreadMarksQuery : public Td::ResultHandler { + public: + void send() { + send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getDialogUnreadMarks()))); + } + + 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()); + } + + auto results = result_ptr.move_as_ok(); + for (auto &result : results) { + td->messages_manager_->on_update_dialog_is_marked_as_unread(DialogId(result), true); + } + + G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); + } + + void on_error(uint64 id, Status status) override { + LOG(ERROR) << "Receive error for GetDialogUnreadMarksQuery: " << status; + status.ignore(); + } +}; + class GetMessagesQuery : public Td::ResultHandler { Promise promise_; @@ -962,6 +988,54 @@ class ReorderPinnedDialogsQuery : public Td::ResultHandler { } }; +class ToggleDialogUnreadMarkQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + bool is_marked_as_unread_; + + public: + explicit ToggleDialogUnreadMarkQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, bool is_marked_as_unread) { + dialog_id_ = dialog_id; + is_marked_as_unread_ = is_marked_as_unread; + auto input_peer = td->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return; + } + + int32 flags = 0; + if (is_marked_as_unread) { + flags |= telegram_api::messages_markDialogUnread::UNREAD_MASK; + } + send_query(G()->net_query_creator().create( + create_storer(telegram_api::messages_markDialogUnread(flags, false /*ignored*/, std::move(input_peer))))); + } + + 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()); + } + + bool result = result_ptr.ok(); + if (!result) { + on_error(id, Status::Error(400, "Toggle dialog mark failed")); + } + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogUnreadMarkQuery")) { + LOG(ERROR) << "Receive error for ToggleDialogUnreadMarkQuery: " << status; + } + td->messages_manager_->on_update_dialog_is_marked_as_unread(dialog_id_, !is_marked_as_unread_); + promise_.set_error(std::move(status)); + } +}; + class GetMessagesViewsQuery : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; @@ -4461,6 +4535,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(has_contact_registered_message); STORE_FLAG(has_last_database_message_id); STORE_FLAG(need_repair_server_unread_count); + STORE_FLAG(is_marked_as_unread); END_STORE_FLAGS(); store(dialog_id, storer); // must be stored at offset 4 @@ -4561,6 +4636,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(has_contact_registered_message); PARSE_FLAG(has_last_database_message_id); PARSE_FLAG(need_repair_server_unread_count); + PARSE_FLAG(is_marked_as_unread); END_PARSE_FLAGS(); parse(dialog_id, parser); // must be stored at offset 4 @@ -8991,6 +9067,10 @@ void MessagesManager::read_history_inbox(DialogId dialog_id, MessageId max_messa } set_dialog_last_read_inbox_message_id(d, max_message_id, server_unread_count, local_unread_count, true, source); + + if (d->is_marked_as_unread) { + set_dialog_is_marked_as_unread(d, false); + } } else { LOG(INFO) << "Receive read inbox about unknown " << dialog_id << " from " << source; } @@ -9692,6 +9772,12 @@ void MessagesManager::start_up() { } } } + + if (!G()->td_db()->get_binlog_pmc()->isset("fetched_marks_as_unread") && !td_->auth_manager_->is_bot()) { + td_->create_handler()->send(); + } + + ttl_db_loop_start(G()->server_time()); } else { G()->td_db()->get_binlog_pmc()->erase("last_server_dialog_date"); G()->td_db()->get_binlog_pmc()->erase("unread_message_count"); @@ -9699,9 +9785,6 @@ void MessagesManager::start_up() { G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id"); } - if (G()->parameters().use_message_db) { - ttl_db_loop_start(G()->server_time()); - } load_calls_db_state(); vector scopes{NotificationSettingsScope::Private, NotificationSettingsScope::Group}; @@ -11136,6 +11219,10 @@ void MessagesManager::on_get_dialogs(vector> set_dialog_is_pinned(d, is_pinned); need_update_dialog_pos = false; } + bool is_marked_as_unread = (dialog->flags_ & telegram_api::dialog::UNREAD_MARK_MASK) != 0; + if (is_marked_as_unread != d->is_marked_as_unread) { + set_dialog_is_marked_as_unread(d, is_marked_as_unread); + } if (need_update_dialog_pos) { update_dialog_pos(d, false, "on_get_dialogs"); @@ -12730,6 +12817,65 @@ void MessagesManager::reorder_pinned_dialogs_on_server(const vector &d td_->create_handler(get_erase_logevent_promise(logevent_id))->send(dialog_ids); } +Status MessagesManager::toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) { + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return Status::Error(6, "Chat not found"); + } + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return Status::Error(6, "Can't access the chat"); + } + + if (is_marked_as_unread == d->is_marked_as_unread) { + return Status::OK(); + } + + set_dialog_is_marked_as_unread(d, is_marked_as_unread); + + toggle_dialog_is_marked_as_unread_on_server(dialog_id, is_marked_as_unread, 0); + return Status::OK(); +} + +class MessagesManager::ToggleDialogIsMarkedAsUnreadOnServerLogEvent { + public: + DialogId dialog_id_; + bool is_marked_as_unread_; + + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_marked_as_unread_); + END_STORE_FLAGS(); + + td::store(dialog_id_, storer); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_marked_as_unread_); + END_PARSE_FLAGS(); + + td::parse(dialog_id_, parser); + } +}; + +void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread, + uint64 logevent_id) { + if (logevent_id == 0 && G()->parameters().use_message_db) { + ToggleDialogIsMarkedAsUnreadOnServerLogEvent logevent; + logevent.dialog_id_ = dialog_id; + logevent.is_marked_as_unread_ = is_marked_as_unread; + + auto storer = LogEventStorerImpl(logevent); + logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), + LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer, storer); + } + + td_->create_handler(get_erase_logevent_promise(logevent_id)) + ->send(dialog_id, is_marked_as_unread); +} + Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) { CHECK(!td_->auth_manager_->is_bot()); @@ -13317,9 +13463,9 @@ tl_object_ptr MessagesManager::get_chat_object(const Dialog *d) { get_chat_photo_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)), get_message_object(d->dialog_id, get_message(d, d->last_message_id)), DialogDate(d->order, d->dialog_id) <= last_dialog_date_ ? d->order : 0, d->pinned_order != DEFAULT_ORDER, - d->order == SPONSORED_DIALOG_ORDER, can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message, - d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), - d->last_read_outbox_message_id.get(), d->unread_mention_count, + d->is_marked_as_unread, d->order == SPONSORED_DIALOG_ORDER, can_report_dialog(d->dialog_id), + d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, + d->last_read_inbox_message_id.get(), d->last_read_outbox_message_id.get(), d->unread_mention_count, get_chat_notification_settings_object(&d->notification_settings), d->reply_markup_message_id.get(), get_draft_message_object(d->draft_message), d->client_data); } @@ -20193,14 +20339,23 @@ void MessagesManager::on_update_dialog_is_marked_as_unread(DialogId dialog_id, b // nothing to do return; } - /* - FIXME + if (is_marked_as_unread == d->is_marked_as_unread) { return; } set_dialog_is_marked_as_unread(d, is_marked_as_unread); - */ +} + +void MessagesManager::set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread) { + CHECK(d != nullptr); + d->is_marked_as_unread = is_marked_as_unread; + on_dialog_updated(d->dialog_id, "set_dialog_is_marked_as_unread"); + + LOG(INFO) << "Set " << d->dialog_id << " is marked as unread to " << is_marked_as_unread; + CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_marked_as_unread"; + send_closure(G()->td(), &Td::send_update, + make_tl_object(d->dialog_id.get(), is_marked_as_unread)); } void MessagesManager::on_create_new_dialog_success(int64 random_id, tl_object_ptr &&updates, @@ -26014,6 +26169,25 @@ void MessagesManager::on_binlog_events(vector &&events) { reorder_pinned_dialogs_on_server(dialog_ids, event.id_); break; } + case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer: { + if (!G()->parameters().use_message_db) { + BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event; + log_event_parse(log_event, event.data_).ensure(); + + auto dialog_id = log_event.dialog_id_; + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { + BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + toggle_dialog_is_marked_as_unread_on_server(dialog_id, log_event.is_marked_as_unread_, event.id_); + break; + } case LogEvent::HandlerType::SaveDialogDraftMessageOnServer: { if (!G()->parameters().use_message_db) { BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 173ee9e4e..4ba4225b2 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -1232,6 +1232,8 @@ class MessagesManager : public Actor { Status toggle_dialog_is_pinned(DialogId dialog_id, bool is_pinned) TD_WARN_UNUSED_RESULT; + Status toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) TD_WARN_UNUSED_RESULT; + Status toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) TD_WARN_UNUSED_RESULT; Status set_pinned_dialogs(vector dialog_ids) TD_WARN_UNUSED_RESULT; @@ -1634,6 +1636,7 @@ class MessagesManager : public Actor { bool is_last_read_inbox_message_id_inited = false; bool is_last_read_outbox_message_id_inited = false; bool need_repair_server_unread_count = false; + bool is_marked_as_unread = false; bool increment_view_counter = false; @@ -1875,6 +1878,7 @@ class MessagesManager : public Actor { class SendMessageLogEvent; class SendScreenshotTakenNotificationMessageLogEvent; class ToggleDialogIsPinnedOnServerLogEvent; + class ToggleDialogIsMarkedAsUnreadOnServerLogEvent; class GetDialogFromServerLogEvent; static constexpr size_t MAX_GROUPED_MESSAGES = 10; // server side limit @@ -2293,8 +2297,12 @@ class MessagesManager : public Actor { void set_dialog_is_pinned(Dialog *d, bool is_pinned); + void set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread); + void toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 logevent_id); + void toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread, uint64 logevent_id); + void reorder_pinned_dialogs_on_server(const vector &dialog_ids, uint64 logevent_id); void set_dialog_reply_markup(Dialog *d, MessageId message_id); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 59fa68662..66b5ec754 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6115,6 +6115,12 @@ void Td::on_request(uint64 id, const td_api::toggleChatIsPinned &request) { answer_ok_query(id, messages_manager_->toggle_dialog_is_pinned(DialogId(request.chat_id_), request.is_pinned_)); } +void Td::on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request) { + CHECK_IS_USER(); + answer_ok_query(id, messages_manager_->toggle_dialog_is_marked_as_unread(DialogId(request.chat_id_), + request.is_marked_as_unread_)); +} + void Td::on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request) { CHECK_IS_USER(); answer_ok_query(id, messages_manager_->toggle_dialog_silent_send_message(DialogId(request.chat_id_), diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 32348030e..2a92c1425 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -574,6 +574,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::toggleChatIsPinned &request); + void on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request); + void on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request); void on_request(uint64 id, const td_api::setPinnedChats &request); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index c12840b79..ac9c89ae9 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -89,6 +89,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p case LogEvent::HandlerType::GetDialogFromServer: case LogEvent::HandlerType::GetChannelDifference: case LogEvent::HandlerType::ReadHistoryInSecretChat: + case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer: events.to_messages_manager.push_back(event.clone()); break; case LogEvent::HandlerType::BinlogPmcMagic: diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index a41cfae61..b59887451 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2253,6 +2253,11 @@ class CliClient final : public Actor { string is_pinned; std::tie(chat_id, is_pinned) = split(args); send_request(make_tl_object(as_chat_id(chat_id), as_bool(is_pinned))); + } else if (op == "tcimar") { + string chat_id; + string is_marked_as_read; + std::tie(chat_id, is_marked_as_read) = split(args); + send_request(make_tl_object(as_chat_id(chat_id), as_bool(is_marked_as_read))); } else if (op == "tcddn") { string chat_id; string default_disable_notification; diff --git a/td/telegram/logevent/LogEvent.h b/td/telegram/logevent/LogEvent.h index 30769ba78..c0e9386bd 100644 --- a/td/telegram/logevent/LogEvent.h +++ b/td/telegram/logevent/LogEvent.h @@ -89,6 +89,7 @@ class LogEvent { ChangeDialogReportSpamStateOnServer = 0x112, GetDialogFromServer = 0x113, ReadHistoryInSecretChat = 0x114, + ToggleDialogIsMarkedAsUnreadOnServer = 0x115, GetChannelDifference = 0x140, ConfigPmcMagic = 0x1f18, BinlogPmcMagic = 0x4327