diff --git a/td/telegram/DialogActionManager.cpp b/td/telegram/DialogActionManager.cpp index 69d226c68..014cec8a5 100644 --- a/td/telegram/DialogActionManager.cpp +++ b/td/telegram/DialogActionManager.cpp @@ -6,13 +6,389 @@ // #include "td/telegram/DialogActionManager.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageSender.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/net/NetQuery.h" +#include "td/telegram/SecretChatsManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/Time.h" + +#include + namespace td { +class SetTypingQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + int32 generation_ = 0; + + public: + explicit SetTypingQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + NetQueryRef send(DialogId dialog_id, tl_object_ptr &&input_peer, + MessageId top_thread_message_id, tl_object_ptr &&action) { + dialog_id_ = dialog_id; + CHECK(input_peer != nullptr); + + int32 flags = 0; + if (top_thread_message_id.is_valid()) { + flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK; + } + auto query = G()->net_query_creator().create(telegram_api::messages_setTyping( + flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get(), std::move(action))); + query->total_timeout_limit_ = 2; + auto result = query.get_weak(); + generation_ = result.generation(); + send_query(std::move(query)); + return result; + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + // ignore result + promise_.set_value(Unit()); + + send_closure_later(G()->dialog_action_manager(), &DialogActionManager::after_set_typing_query, dialog_id_, + generation_); + } + + void on_error(Status status) final { + if (status.code() == NetQuery::Canceled) { + return promise_.set_value(Unit()); + } + + if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) { + LOG(INFO) << "Receive error for set typing: " << status; + } + promise_.set_error(std::move(status)); + + send_closure_later(G()->dialog_action_manager(), &DialogActionManager::after_set_typing_query, dialog_id_, + generation_); + } +}; + DialogActionManager::DialogActionManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + active_dialog_action_timeout_.set_callback(on_active_dialog_action_timeout_callback); + active_dialog_action_timeout_.set_callback_data(static_cast(this)); } void DialogActionManager::tear_down() { parent_.reset(); } +void DialogActionManager::on_active_dialog_action_timeout_callback(void *dialog_action_manager_ptr, + int64 dialog_id_int) { + if (G()->close_flag()) { + return; + } + + auto dialog_action_manager = static_cast(dialog_action_manager_ptr); + send_closure_later(dialog_action_manager->actor_id(dialog_action_manager), + &DialogActionManager::on_active_dialog_action_timeout, DialogId(dialog_id_int)); +} + +void DialogActionManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, + DialogId typing_dialog_id, DialogAction action, int32 date, + MessageContentType message_content_type) { + if (td_->auth_manager_->is_bot() || !typing_dialog_id.is_valid()) { + return; + } + if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) { + LOG(ERROR) << "Ignore " << action << " in the message thread of " << top_thread_message_id; + return; + } + + auto dialog_type = dialog_id.get_type(); + if (action == DialogAction::get_speaking_action()) { + if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) { + LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id; + return; + } + return td_->messages_manager_->on_dialog_speaking_action(dialog_id, typing_dialog_id, date); + } + + if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return; + } + + auto typing_dialog_type = typing_dialog_id.get_type(); + if (typing_dialog_type != DialogType::User && dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) { + LOG(ERROR) << "Ignore " << action << " of " << typing_dialog_id << " in " << dialog_id; + return; + } + + { + auto message_import_progress = action.get_importing_messages_action_progress(); + if (message_import_progress >= 0) { + // TODO + return; + } + } + + { + auto clicking_info = action.get_clicking_animated_emoji_action_info(); + if (!clicking_info.data.empty()) { + if (date > G()->unix_time() - 10 && dialog_type == DialogType::User && dialog_id == typing_dialog_id) { + td_->messages_manager_->on_message_animated_emoji_clicked( + {dialog_id, MessageId(ServerMessageId(clicking_info.message_id))}, std::move(clicking_info.emoji), + std::move(clicking_info.data)); + } + return; + } + } + + if (is_unsent_animated_emoji_click(td_, dialog_id, action)) { + LOG(DEBUG) << "Ignore unsent " << action; + return; + } + + if (!td_->messages_manager_->have_dialog(dialog_id)) { + LOG(DEBUG) << "Ignore " << action << " in unknown " << dialog_id; + return; + } + + if (typing_dialog_type == DialogType::User) { + if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) { + LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id(); + return; + } + } else { + if (!td_->dialog_manager_->have_dialog_info_force(typing_dialog_id, "on_dialog_action")) { + LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id; + return; + } + td_->dialog_manager_->force_create_dialog(typing_dialog_id, "on_dialog_action", true); + if (!td_->messages_manager_->have_dialog(typing_dialog_id)) { + LOG(ERROR) << "Failed to create typing " << typing_dialog_id; + return; + } + } + + bool is_canceled = action == DialogAction(); + if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) { + td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date); + } + + if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { + CHECK(typing_dialog_type == DialogType::User); + auto user_id = typing_dialog_id.get_user_id(); + if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) && + !td_->messages_manager_->is_dialog_opened(dialog_id) && !is_canceled) { + return; + } + } + + if (is_canceled) { + // passed top_thread_message_id must be ignored + auto actions_it = active_dialog_actions_.find(dialog_id); + if (actions_it == active_dialog_actions_.end()) { + return; + } + + auto &active_actions = actions_it->second; + auto it = std::find_if( + active_actions.begin(), active_actions.end(), + [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); + if (it == active_actions.end()) { + return; + } + + if (!(typing_dialog_type == DialogType::User && + td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) && + !it->action.is_canceled_by_message_of_type(message_content_type)) { + return; + } + + LOG(DEBUG) << "Cancel action of " << typing_dialog_id << " in " << dialog_id; + top_thread_message_id = it->top_thread_message_id; + active_actions.erase(it); + if (active_actions.empty()) { + active_dialog_actions_.erase(dialog_id); + LOG(DEBUG) << "Cancel action timeout in " << dialog_id; + active_dialog_action_timeout_.cancel_timeout(dialog_id.get()); + } + } else { + if (date < G()->unix_time() - DIALOG_ACTION_TIMEOUT - 60) { + LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date; + return; + } + auto &active_actions = active_dialog_actions_[dialog_id]; + auto it = std::find_if( + active_actions.begin(), active_actions.end(), + [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); + MessageId prev_top_thread_message_id; + DialogAction prev_action; + if (it != active_actions.end()) { + LOG(DEBUG) << "Re-add action of " << typing_dialog_id << " in " << dialog_id; + prev_top_thread_message_id = it->top_thread_message_id; + prev_action = it->action; + active_actions.erase(it); + } else { + LOG(DEBUG) << "Add action of " << typing_dialog_id << " in " << dialog_id; + } + + active_actions.emplace_back(top_thread_message_id, typing_dialog_id, action, Time::now()); + if (top_thread_message_id == prev_top_thread_message_id && action == prev_action) { + return; + } + if (top_thread_message_id != prev_top_thread_message_id && prev_top_thread_message_id.is_valid()) { + send_update_chat_action(dialog_id, prev_top_thread_message_id, typing_dialog_id, DialogAction()); + } + if (active_actions.size() == 1u) { + LOG(DEBUG) << "Set action timeout in " << dialog_id; + active_dialog_action_timeout_.set_timeout_in(dialog_id.get(), DIALOG_ACTION_TIMEOUT); + } + } + + if (top_thread_message_id.is_valid()) { + send_update_chat_action(dialog_id, MessageId(), typing_dialog_id, action); + } + send_update_chat_action(dialog_id, top_thread_message_id, typing_dialog_id, action); +} + +void DialogActionManager::send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, + DialogId typing_dialog_id, const DialogAction &action) { + if (td_->auth_manager_->is_bot()) { + return; + } + + LOG(DEBUG) << "Send " << action << " of " << typing_dialog_id << " in thread of " << top_thread_message_id << " in " + << dialog_id; + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "updateChatAction"), top_thread_message_id.get(), + get_message_sender_object(td_, typing_dialog_id, "send_update_chat_action"), + action.get_chat_action_object())); +} + +void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, + Promise &&promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "send_dialog_action")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (top_thread_message_id != MessageId() && + (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server())) { + return promise.set_error(Status::Error(400, "Invalid message thread specified")); + } + + if (td_->dialog_manager_->is_forum_channel(dialog_id) && !top_thread_message_id.is_valid()) { + top_thread_message_id = MessageId(ServerMessageId(1)); + } + + tl_object_ptr input_peer; + if (action == DialogAction::get_speaking_action()) { + input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return promise.set_error(Status::Error(400, "Have no access to the chat")); + } + } else { + if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (td_->auth_manager_->is_bot()) { + return promise.set_error(Status::Error(400, "Have no write access to the chat")); + } + return promise.set_value(Unit()); + } + + if (td_->dialog_manager_->is_dialog_action_unneeded(dialog_id)) { + LOG(INFO) << "Skip unneeded " << action << " in " << dialog_id; + return promise.set_value(Unit()); + } + + input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + } + + if (dialog_id.get_type() == DialogType::SecretChat) { + send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(), + action.get_secret_input_send_message_action()); + promise.set_value(Unit()); + return; + } + + auto new_query_ref = + td_->create_handler(std::move(promise)) + ->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action()); + if (td_->auth_manager_->is_bot()) { + return; + } + + auto &query_ref = set_typing_query_[dialog_id]; + if (!query_ref.empty()) { + LOG(INFO) << "Cancel previous send chat action query"; + cancel_query(query_ref); + } + query_ref = std::move(new_query_ref); +} + +void DialogActionManager::cancel_send_dialog_action_queries(DialogId dialog_id) { + auto it = set_typing_query_.find(dialog_id); + if (it == set_typing_query_.end()) { + return; + } + if (!it->second.empty()) { + cancel_query(it->second); + } + set_typing_query_.erase(it); +} + +void DialogActionManager::after_set_typing_query(DialogId dialog_id, int32 generation) { + auto it = set_typing_query_.find(dialog_id); + if (it != set_typing_query_.end() && (!it->second.is_alive() || it->second.generation() == generation)) { + set_typing_query_.erase(it); + } +} + +void DialogActionManager::on_active_dialog_action_timeout(DialogId dialog_id) { + LOG(DEBUG) << "Receive active dialog action timeout in " << dialog_id; + auto actions_it = active_dialog_actions_.find(dialog_id); + if (actions_it == active_dialog_actions_.end()) { + return; + } + CHECK(!actions_it->second.empty()); + + auto now = Time::now(); + DialogId prev_typing_dialog_id; + while (actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT < now + 0.1) { + CHECK(actions_it->second[0].typing_dialog_id != prev_typing_dialog_id); + prev_typing_dialog_id = actions_it->second[0].typing_dialog_id; + on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, + DialogAction(), 0); + + actions_it = active_dialog_actions_.find(dialog_id); + if (actions_it == active_dialog_actions_.end()) { + return; + } + CHECK(!actions_it->second.empty()); + } + + LOG(DEBUG) << "Schedule next action timeout in " << dialog_id; + active_dialog_action_timeout_.add_timeout_in(dialog_id.get(), + actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT - now); +} + +void DialogActionManager::clear_active_dialog_actions(DialogId dialog_id) { + LOG(DEBUG) << "Clear active dialog actions in " << dialog_id; + auto actions_it = active_dialog_actions_.find(dialog_id); + while (actions_it != active_dialog_actions_.end()) { + CHECK(!actions_it->second.empty()); + on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, + DialogAction(), 0); + actions_it = active_dialog_actions_.find(dialog_id); + } +} + } // namespace td diff --git a/td/telegram/DialogActionManager.h b/td/telegram/DialogActionManager.h index ba81f8090..bfc9b5374 100644 --- a/td/telegram/DialogActionManager.h +++ b/td/telegram/DialogActionManager.h @@ -6,9 +6,18 @@ // #pragma once +#include "td/telegram/DialogAction.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/MessageContentType.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/net/NetQuery.h" +#include "td/telegram/td_api.h" + #include "td/actor/actor.h" +#include "td/actor/MultiTimeout.h" #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" namespace td { @@ -18,9 +27,51 @@ class DialogActionManager final : public Actor { public: DialogActionManager(Td *td, ActorShared<> parent); + void on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, + DialogAction action, int32 date, + MessageContentType message_content_type = MessageContentType::None); + + void send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, + Promise &&promise); + + void cancel_send_dialog_action_queries(DialogId dialog_id); + + void after_set_typing_query(DialogId dialog_id, int32 generation); + + void clear_active_dialog_actions(DialogId dialog_id); + private: + static constexpr double DIALOG_ACTION_TIMEOUT = 5.5; + void tear_down() final; + void send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, + const DialogAction &action); + + static void on_active_dialog_action_timeout_callback(void *dialog_action_manager_ptr, int64 dialog_id_int); + + void on_active_dialog_action_timeout(DialogId dialog_id); + + struct ActiveDialogAction { + MessageId top_thread_message_id; + DialogId typing_dialog_id; + DialogAction action; + double start_time; + + ActiveDialogAction(MessageId top_thread_message_id, DialogId typing_dialog_id, DialogAction action, + double start_time) + : top_thread_message_id(top_thread_message_id) + , typing_dialog_id(typing_dialog_id) + , action(std::move(action)) + , start_time(start_time) { + } + }; + FlatHashMap, DialogIdHash> active_dialog_actions_; + + MultiTimeout active_dialog_action_timeout_{"ActiveDialogActionTimeout"}; + + FlatHashMap set_typing_query_; + Td *td_; ActorShared<> parent_; }; diff --git a/td/telegram/Global.h b/td/telegram/Global.h index b00ee601a..f45a0a7b0 100644 --- a/td/telegram/Global.h +++ b/td/telegram/Global.h @@ -41,6 +41,7 @@ class CallManager; class ConfigManager; class ConnectionCreator; class ContactsManager; +class DialogActionManager; class DialogFilterManager; class DialogManager; class DownloadManager; @@ -246,6 +247,13 @@ class Global final : public ActorContext { contacts_manager_ = contacts_manager; } + ActorId dialog_action_manager() const { + return dialog_action_manager_; + } + void set_dialog_action_manager(ActorId dialog_action_manager) { + dialog_action_manager_ = std::move(dialog_action_manager); + } + ActorId dialog_filter_manager() const { return dialog_filter_manager_; } @@ -562,6 +570,7 @@ class Global final : public ActorContext { ActorId call_manager_; ActorId config_manager_; ActorId contacts_manager_; + ActorId dialog_action_manager_; ActorId dialog_filter_manager_; ActorId dialog_manager_; ActorId download_manager_; diff --git a/td/telegram/GroupCallManager.cpp b/td/telegram/GroupCallManager.cpp index 290ea37ae..eebaae812 100644 --- a/td/telegram/GroupCallManager.cpp +++ b/td/telegram/GroupCallManager.cpp @@ -10,6 +10,7 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogAction.h" +#include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DialogParticipantFilter.h" #include "td/telegram/Global.h" @@ -1099,8 +1100,8 @@ void GroupCallManager::on_send_speaking_action_timeout(GroupCallId group_call_id pending_send_speaking_action_timeout_.add_timeout_in(group_call_id.get(), 4.0); - td_->messages_manager_->send_dialog_action(group_call->dialog_id, MessageId(), DialogAction::get_speaking_action(), - Promise()); + td_->dialog_action_manager_->send_dialog_action(group_call->dialog_id, MessageId(), + DialogAction::get_speaking_action(), Promise()); } void GroupCallManager::on_recent_speaker_update_timeout_callback(void *group_call_manager_ptr, diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index b7998e880..6cd786634 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -16,7 +16,9 @@ #include "td/telegram/ChatReactions.hpp" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" +#include "td/telegram/DialogAction.h" #include "td/telegram/DialogActionBar.h" +#include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogDb.h" #include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilterManager.h" @@ -52,7 +54,6 @@ #include "td/telegram/MessageSender.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" -#include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationGroupInfo.hpp" #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationManager.h" @@ -3561,59 +3562,6 @@ class SendBotRequestedPeerQuery final : public Td::ResultHandler { } }; -class SetTypingQuery final : public Td::ResultHandler { - Promise promise_; - DialogId dialog_id_; - int32 generation_ = 0; - - public: - explicit SetTypingQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - NetQueryRef send(DialogId dialog_id, tl_object_ptr &&input_peer, - MessageId top_thread_message_id, tl_object_ptr &&action) { - dialog_id_ = dialog_id; - CHECK(input_peer != nullptr); - - int32 flags = 0; - if (top_thread_message_id.is_valid()) { - flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK; - } - auto query = G()->net_query_creator().create(telegram_api::messages_setTyping( - flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get(), std::move(action))); - query->total_timeout_limit_ = 2; - auto result = query.get_weak(); - generation_ = result.generation(); - send_query(std::move(query)); - return result; - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - // ignore result - promise_.set_value(Unit()); - - send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_); - } - - void on_error(Status status) final { - if (status.code() == NetQuery::Canceled) { - return promise_.set_value(Unit()); - } - - if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) { - LOG(INFO) << "Receive error for set typing: " << status; - } - promise_.set_error(std::move(status)); - - send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_); - } -}; - class DeleteMessagesQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -5282,9 +5230,6 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) pending_send_dialog_action_timeout_.set_callback(on_pending_send_dialog_action_timeout_callback); pending_send_dialog_action_timeout_.set_callback_data(static_cast(this)); - active_dialog_action_timeout_.set_callback(on_active_dialog_action_timeout_callback); - active_dialog_action_timeout_.set_callback_data(static_cast(this)); - preload_folder_dialog_list_timeout_.set_callback(on_preload_folder_dialog_list_timeout_callback); preload_folder_dialog_list_timeout_.set_callback_data(static_cast(this)); @@ -5407,16 +5352,6 @@ void MessagesManager::on_pending_send_dialog_action_timeout_callback(void *messa DialogId(dialog_id_int)); } -void MessagesManager::on_active_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { - if (G()->close_flag()) { - return; - } - - auto messages_manager = static_cast(messages_manager_ptr); - send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_active_dialog_action_timeout, - DialogId(dialog_id_int)); -} - void MessagesManager::on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int) { if (G()->close_flag()) { return; @@ -6740,170 +6675,20 @@ void MessagesManager::on_update_created_public_broadcasts(vector chan created_public_broadcasts_ = std::move(channel_ids); } -void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, - DialogAction action, int32 date, MessageContentType message_content_type) { - if (td_->auth_manager_->is_bot() || !typing_dialog_id.is_valid()) { - return; +void MessagesManager::on_dialog_speaking_action(DialogId dialog_id, DialogId speaking_dialog_id, int32 date) { + const Dialog *d = get_dialog_force(dialog_id, "on_dialog_speaking_action"); + if (d != nullptr && d->active_group_call_id.is_valid()) { + auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id); + td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, speaking_dialog_id, false, date); } - if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) { - LOG(ERROR) << "Ignore " << action << " in the message thread of " << top_thread_message_id; - return; +} + +void MessagesManager::on_message_animated_emoji_clicked(MessageFullId message_full_id, string &&emoji, string &&data) { + const auto *m = get_message_force(message_full_id, "on_message_animated_emoji_clicked"); + if (m != nullptr) { + on_message_content_animated_emoji_clicked(m->content.get(), message_full_id, td_, std::move(emoji), + std::move(data)); } - - auto dialog_type = dialog_id.get_type(); - if (action == DialogAction::get_speaking_action()) { - if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) { - LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id; - return; - } - const Dialog *d = get_dialog_force(dialog_id, "on_dialog_action"); - if (d != nullptr && d->active_group_call_id.is_valid()) { - auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id); - td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, typing_dialog_id, false, date); - } - return; - } - - if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { - return; - } - - auto typing_dialog_type = typing_dialog_id.get_type(); - if (typing_dialog_type != DialogType::User && dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) { - LOG(ERROR) << "Ignore " << action << " of " << typing_dialog_id << " in " << dialog_id; - return; - } - - { - auto message_import_progress = action.get_importing_messages_action_progress(); - if (message_import_progress >= 0) { - // TODO - return; - } - } - - { - auto clicking_info = action.get_clicking_animated_emoji_action_info(); - if (!clicking_info.data.empty()) { - if (date > G()->unix_time() - 10 && dialog_type == DialogType::User && dialog_id == typing_dialog_id) { - MessageFullId message_full_id{dialog_id, MessageId(ServerMessageId(clicking_info.message_id))}; - auto *m = get_message_force(message_full_id, "on_dialog_action"); - if (m != nullptr) { - on_message_content_animated_emoji_clicked(m->content.get(), message_full_id, td_, - std::move(clicking_info.emoji), std::move(clicking_info.data)); - } - } - return; - } - } - - if (is_unsent_animated_emoji_click(td_, dialog_id, action)) { - LOG(DEBUG) << "Ignore unsent " << action; - return; - } - - if (!have_dialog(dialog_id)) { - LOG(DEBUG) << "Ignore " << action << " in unknown " << dialog_id; - return; - } - - if (typing_dialog_type == DialogType::User) { - if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) { - LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id(); - return; - } - } else { - if (!td_->dialog_manager_->have_dialog_info_force(typing_dialog_id, "on_dialog_action")) { - LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id; - return; - } - force_create_dialog(typing_dialog_id, "on_dialog_action", true); - if (!have_dialog(typing_dialog_id)) { - LOG(ERROR) << "Failed to create typing " << typing_dialog_id; - return; - } - } - - bool is_canceled = action == DialogAction(); - if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) { - td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date); - } - - if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { - CHECK(typing_dialog_type == DialogType::User); - auto user_id = typing_dialog_id.get_user_id(); - if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) && - !is_dialog_opened(dialog_id) && !is_canceled) { - return; - } - } - - if (is_canceled) { - // passed top_thread_message_id must be ignored - auto actions_it = active_dialog_actions_.find(dialog_id); - if (actions_it == active_dialog_actions_.end()) { - return; - } - - auto &active_actions = actions_it->second; - auto it = std::find_if( - active_actions.begin(), active_actions.end(), - [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); - if (it == active_actions.end()) { - return; - } - - if (!(typing_dialog_type == DialogType::User && - td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) && - !it->action.is_canceled_by_message_of_type(message_content_type)) { - return; - } - - LOG(DEBUG) << "Cancel action of " << typing_dialog_id << " in " << dialog_id; - top_thread_message_id = it->top_thread_message_id; - active_actions.erase(it); - if (active_actions.empty()) { - active_dialog_actions_.erase(dialog_id); - LOG(DEBUG) << "Cancel action timeout in " << dialog_id; - active_dialog_action_timeout_.cancel_timeout(dialog_id.get()); - } - } else { - if (date < G()->unix_time() - DIALOG_ACTION_TIMEOUT - 60) { - LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date; - return; - } - auto &active_actions = active_dialog_actions_[dialog_id]; - auto it = std::find_if( - active_actions.begin(), active_actions.end(), - [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; }); - MessageId prev_top_thread_message_id; - DialogAction prev_action; - if (it != active_actions.end()) { - LOG(DEBUG) << "Re-add action of " << typing_dialog_id << " in " << dialog_id; - prev_top_thread_message_id = it->top_thread_message_id; - prev_action = it->action; - active_actions.erase(it); - } else { - LOG(DEBUG) << "Add action of " << typing_dialog_id << " in " << dialog_id; - } - - active_actions.emplace_back(top_thread_message_id, typing_dialog_id, action, Time::now()); - if (top_thread_message_id == prev_top_thread_message_id && action == prev_action) { - return; - } - if (top_thread_message_id != prev_top_thread_message_id && prev_top_thread_message_id.is_valid()) { - send_update_chat_action(dialog_id, prev_top_thread_message_id, typing_dialog_id, DialogAction()); - } - if (active_actions.size() == 1u) { - LOG(DEBUG) << "Set action timeout in " << dialog_id; - active_dialog_action_timeout_.set_timeout_in(dialog_id.get(), DIALOG_ACTION_TIMEOUT); - } - } - - if (top_thread_message_id.is_valid()) { - send_update_chat_action(dialog_id, MessageId(), typing_dialog_id, action); - } - send_update_chat_action(dialog_id, top_thread_message_id, typing_dialog_id, action); } void MessagesManager::cancel_dialog_action(DialogId dialog_id, const Message *m) { @@ -6913,8 +6698,8 @@ void MessagesManager::cancel_dialog_action(DialogId dialog_id, const Message *m) return; } - on_dialog_action(dialog_id, MessageId() /*ignored*/, get_message_sender(m), DialogAction(), m->date, - m->content->get_type()); + td_->dialog_action_manager_->on_dialog_action(dialog_id, MessageId() /*ignored*/, get_message_sender(m), + DialogAction(), m->date, m->content->get_type()); } void MessagesManager::add_postponed_channel_update(DialogId dialog_id, tl_object_ptr &&update, @@ -23460,15 +23245,7 @@ void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dial return promise.set_error(Status::Error(400, "Can't access specified message sender chat")); } - { - auto it = set_typing_query_.find(dialog_id); - if (it != set_typing_query_.end()) { - if (!it->second.empty()) { - cancel_query(it->second); - } - set_typing_query_.erase(it); - } - } + td_->dialog_action_manager_->cancel_send_dialog_action_queries(dialog_id); td_->create_handler(std::move(promise))->send(dialog_id, message_sender_dialog_id); @@ -29091,21 +28868,6 @@ void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool fr get_chat_id_object(d->dialog_id, "updateChatHasScheduledMessages"), has_scheduled_messages)); } -void MessagesManager::send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, - DialogId typing_dialog_id, const DialogAction &action) { - if (td_->auth_manager_->is_bot()) { - return; - } - - LOG(DEBUG) << "Send " << action << " of " << typing_dialog_id << " in thread of " << top_thread_message_id << " in " - << dialog_id; - send_closure(G()->td(), &Td::send_update, - td_api::make_object( - get_chat_id_object(dialog_id, "updateChatAction"), top_thread_message_id.get(), - get_message_sender_object(td_, typing_dialog_id, "send_update_chat_action"), - action.get_chat_action_object())); -} - void MessagesManager::on_send_message_get_quick_ack(int64 random_id) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { @@ -31388,72 +31150,6 @@ MessagesManager::NotificationInfo *MessagesManager::add_dialog_notification_info return d->notification_info.get(); } -void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, - Promise &&promise) { - if (!have_dialog_force(dialog_id, "send_dialog_action")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (top_thread_message_id != MessageId() && - (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server())) { - return promise.set_error(Status::Error(400, "Invalid message thread specified")); - } - - if (td_->dialog_manager_->is_forum_channel(dialog_id) && !top_thread_message_id.is_valid()) { - top_thread_message_id = MessageId(ServerMessageId(1)); - } - - tl_object_ptr input_peer; - if (action == DialogAction::get_speaking_action()) { - input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - return promise.set_error(Status::Error(400, "Have no access to the chat")); - } - } else { - auto can_send_status = can_send_message(dialog_id); - if (can_send_status.is_error()) { - if (td_->auth_manager_->is_bot()) { - return promise.set_error(std::move(can_send_status)); - } - return promise.set_value(Unit()); - } - - if (td_->dialog_manager_->is_dialog_action_unneeded(dialog_id)) { - LOG(INFO) << "Skip unneeded " << action << " in " << dialog_id; - return promise.set_value(Unit()); - } - - input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - } - - if (dialog_id.get_type() == DialogType::SecretChat) { - send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(), - action.get_secret_input_send_message_action()); - promise.set_value(Unit()); - return; - } - - auto new_query_ref = - td_->create_handler(std::move(promise)) - ->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action()); - if (td_->auth_manager_->is_bot()) { - return; - } - - auto &query_ref = set_typing_query_[dialog_id]; - if (!query_ref.empty()) { - LOG(INFO) << "Cancel previous send chat action query"; - cancel_query(query_ref); - } - query_ref = std::move(new_query_ref); -} - -void MessagesManager::after_set_typing_query(DialogId dialog_id, int32 generation) { - auto it = set_typing_query_.find(dialog_id); - if (it != set_typing_query_.end() && (!it->second.is_alive() || it->second.generation() == generation)) { - set_typing_query_.erase(it); - } -} - void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { LOG(INFO) << "Receive send_chat_action timeout in " << dialog_id; Dialog *d = get_dialog(dialog_id); @@ -31509,46 +31205,8 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { return; } LOG(INFO) << "Send " << action << " in " << dialog_id; - send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), Promise()); -} - -void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) { - LOG(DEBUG) << "Receive active dialog action timeout in " << dialog_id; - auto actions_it = active_dialog_actions_.find(dialog_id); - if (actions_it == active_dialog_actions_.end()) { - return; - } - CHECK(!actions_it->second.empty()); - - auto now = Time::now(); - DialogId prev_typing_dialog_id; - while (actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT < now + 0.1) { - CHECK(actions_it->second[0].typing_dialog_id != prev_typing_dialog_id); - prev_typing_dialog_id = actions_it->second[0].typing_dialog_id; - on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, - DialogAction(), 0); - - actions_it = active_dialog_actions_.find(dialog_id); - if (actions_it == active_dialog_actions_.end()) { - return; - } - CHECK(!actions_it->second.empty()); - } - - LOG(DEBUG) << "Schedule next action timeout in " << dialog_id; - active_dialog_action_timeout_.add_timeout_in(dialog_id.get(), - actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT - now); -} - -void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) { - LOG(DEBUG) << "Clear active dialog actions in " << dialog_id; - auto actions_it = active_dialog_actions_.find(dialog_id); - while (actions_it != active_dialog_actions_.end()) { - CHECK(!actions_it->second.empty()); - on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id, - DialogAction(), 0); - actions_it = active_dialog_actions_.find(dialog_id); - } + td_->dialog_action_manager_->send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), + Promise()); } void MessagesManager::get_dialog_filter_dialog_count(td_api::object_ptr filter, @@ -35520,7 +35178,7 @@ bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_sen if (dialog_type == DialogType::Channel && is_removed) { remove_all_dialog_notifications(d, false, source); remove_all_dialog_notifications(d, true, source); - clear_active_dialog_actions(dialog_id); + td_->dialog_action_manager_->clear_active_dialog_actions(dialog_id); } } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 5a0315214..93b5a1788 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -10,7 +10,6 @@ #include "td/telegram/BackgroundInfo.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatReactions.h" -#include "td/telegram/DialogAction.h" #include "td/telegram/DialogDate.h" #include "td/telegram/DialogDb.h" #include "td/telegram/DialogFilterDialogInfo.h" @@ -342,9 +341,9 @@ class MessagesManager final : public Actor { void on_update_created_public_broadcasts(vector channel_ids); - void on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, - DialogAction action, int32 date, - MessageContentType message_content_type = MessageContentType::None); + void on_dialog_speaking_action(DialogId dialog_id, DialogId speaking_dialog_id, int32 date); + + void on_message_animated_emoji_clicked(MessageFullId message_full_id, string &&emoji, string &&data); void read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count, const char *source); @@ -491,11 +490,6 @@ class MessagesManager final : public Actor { td_api::object_ptr &&scheduling_state, Promise &&promise); - void send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, - Promise &&promise); - - void after_set_typing_query(DialogId dialog_id, int32 generation); - void get_dialog_filter_dialog_count(td_api::object_ptr filter, Promise &&promise); void add_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id); @@ -1642,8 +1636,6 @@ class MessagesManager final : public Actor { static constexpr int32 SCHEDULE_WHEN_ONLINE_DATE = 2147483646; - static constexpr double DIALOG_ACTION_TIMEOUT = 5.5; - static constexpr const char *DELETE_MESSAGE_USER_REQUEST_SOURCE = "user request"; static constexpr bool DROP_SEND_MESSAGE_UPDATES = false; @@ -2403,9 +2395,6 @@ class MessagesManager final : public Actor { void send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion); - void send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id, - const DialogAction &action); - void repair_dialog_action_bar(Dialog *d, const char *source); void hide_dialog_action_bar(Dialog *d); @@ -2601,10 +2590,6 @@ class MessagesManager final : public Actor { void on_send_dialog_action_timeout(DialogId dialog_id); - void on_active_dialog_action_timeout(DialogId dialog_id); - - void clear_active_dialog_actions(DialogId dialog_id); - void cancel_dialog_action(DialogId dialog_id, const Message *m); Dialog *get_dialog_by_message_id(MessageId message_id); @@ -2999,8 +2984,6 @@ class MessagesManager final : public Actor { static void on_pending_send_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int); - static void on_active_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int); - static void on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int); static void on_update_viewed_messages_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int); @@ -3274,23 +3257,6 @@ class MessagesManager final : public Actor { FlatHashMap, StoryFullIdHash> story_to_replied_media_timestamp_messages_; - struct ActiveDialogAction { - MessageId top_thread_message_id; - DialogId typing_dialog_id; - DialogAction action; - double start_time; - - ActiveDialogAction(MessageId top_thread_message_id, DialogId typing_dialog_id, DialogAction action, - double start_time) - : top_thread_message_id(top_thread_message_id) - , typing_dialog_id(typing_dialog_id) - , action(std::move(action)) - , start_time(start_time) { - } - }; - - FlatHashMap, DialogIdHash> active_dialog_actions_; - FlatHashMap notification_group_id_to_dialog_id_; uint64 current_message_edit_generation_ = 0; @@ -3343,7 +3309,6 @@ class MessagesManager final : public Actor { MultiTimeout pending_unload_dialog_timeout_{"PendingUnloadDialogTimeout"}; MultiTimeout dialog_unmute_timeout_{"DialogUnmuteTimeout"}; MultiTimeout pending_send_dialog_action_timeout_{"PendingSendDialogActionTimeout"}; - MultiTimeout active_dialog_action_timeout_{"ActiveDialogActionTimeout"}; MultiTimeout preload_folder_dialog_list_timeout_{"PreloadFolderDialogListTimeout"}; MultiTimeout update_viewed_messages_timeout_{"UpdateViewedMessagesTimeout"}; MultiTimeout send_update_chat_read_inbox_timeout_{"SendUpdateChatReadInboxTimeout"}; @@ -3445,8 +3410,6 @@ class MessagesManager final : public Actor { }; FlatHashMap yet_unsent_media_queues_; - FlatHashMap set_typing_query_; - WaitFreeHashMap message_full_id_to_file_source_id_; FlatHashMap last_outgoing_forwarded_message_date_; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 87e50942d..d624ba820 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -4016,6 +4016,7 @@ void Td::init_managers() { country_info_manager_actor_ = register_actor("CountryInfoManager", country_info_manager_.get()); dialog_action_manager_ = make_unique(this, create_reference()); dialog_action_manager_actor_ = register_actor("DialogActionManager", dialog_action_manager_.get()); + G()->set_dialog_action_manager(dialog_action_manager_actor_.get()); dialog_filter_manager_ = make_unique(this, create_reference()); dialog_filter_manager_actor_ = register_actor("DialogFilterManager", dialog_filter_manager_.get()); G()->set_dialog_filter_manager(dialog_filter_manager_actor_.get()); @@ -5980,8 +5981,8 @@ void Td::on_request(uint64 id, const td_api::deleteChatReplyMarkup &request) { void Td::on_request(uint64 id, td_api::sendChatAction &request) { CREATE_OK_REQUEST_PROMISE(); - messages_manager_->send_dialog_action(DialogId(request.chat_id_), MessageId(request.message_thread_id_), - DialogAction(std::move(request.action_)), std::move(promise)); + dialog_action_manager_->send_dialog_action(DialogId(request.chat_id_), MessageId(request.message_thread_id_), + DialogAction(std::move(request.action_)), std::move(promise)); } void Td::on_request(uint64 id, td_api::forwardMessages &request) { diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 6d1a8ad2f..35c6ec39d 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -20,6 +20,7 @@ #include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogAction.h" +#include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogFilterManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogInviteLink.h" @@ -3888,29 +3889,30 @@ bool UpdatesManager::is_channel_pts_update(const telegram_api::Update *update) { void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { DialogId dialog_id(UserId(update->user_id_)); - td_->messages_manager_->on_dialog_action(dialog_id, MessageId(), dialog_id, DialogAction(std::move(update->action_)), - get_short_update_date()); + td_->dialog_action_manager_->on_dialog_action(dialog_id, MessageId(), dialog_id, + DialogAction(std::move(update->action_)), get_short_update_date()); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->messages_manager_->on_dialog_action(DialogId(ChatId(update->chat_id_)), MessageId(), DialogId(update->from_id_), - DialogAction(std::move(update->action_)), get_short_update_date()); + td_->dialog_action_manager_->on_dialog_action(DialogId(ChatId(update->chat_id_)), MessageId(), + DialogId(update->from_id_), DialogAction(std::move(update->action_)), + get_short_update_date()); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->messages_manager_->on_dialog_action(DialogId(ChannelId(update->channel_id_)), - MessageId(ServerMessageId(update->top_msg_id_)), DialogId(update->from_id_), - DialogAction(std::move(update->action_)), get_short_update_date()); + td_->dialog_action_manager_->on_dialog_action( + DialogId(ChannelId(update->channel_id_)), MessageId(ServerMessageId(update->top_msg_id_)), + DialogId(update->from_id_), DialogAction(std::move(update->action_)), get_short_update_date()); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { SecretChatId secret_chat_id(update->chat_id_); UserId user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); - td_->messages_manager_->on_dialog_action(DialogId(secret_chat_id), MessageId(), DialogId(user_id), - DialogAction::get_typing_action(), get_short_update_date()); + td_->dialog_action_manager_->on_dialog_action(DialogId(secret_chat_id), MessageId(), DialogId(user_id), + DialogAction::get_typing_action(), get_short_update_date()); promise.set_value(Unit()); }