diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 08d488077..2161ec780 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -8158,6 +8158,13 @@ loadQuickReplyShortcutMessages shortcut_id:int32 = Ok; //@message_ids Unique identifiers of the messages deleteQuickReplyShortcutMessages shortcut_id:int32 message_ids:vector = Ok; +//@description Adds a message to a quick reply shortcut. If shortcut doesn't exist and there are less than getOption("quick_reply_shortcut_count_max") shortcuts, then a new shortcut is created. +//-The shortcut must not contain more than getOption("quick_reply_shortcut_message_count_max") messages after adding the new message. Returns the added message +//@shortcut_name Name of the target shortcut +//@reply_to_message_id Identifier of a quick reply message in the same shortcut to be replied; pass 0 if none +//@input_message_content The content of the message to be added; inputMessagePoll and inputMessageForwarded aren't supported +addQuickReplyShortcutMessage shortcut_name:string reply_to_message_id:int53 input_message_content:InputMessageContent = QuickReplyMessage; + //@description Returns list of custom emojis, which can be used as forum topic icon by all users getForumTopicDefaultIcons = Stickers; diff --git a/td/telegram/QuickReplyManager.cpp b/td/telegram/QuickReplyManager.cpp index 98f15213a..c0d43c448 100644 --- a/td/telegram/QuickReplyManager.cpp +++ b/td/telegram/QuickReplyManager.cpp @@ -223,6 +223,132 @@ class DeleteQuickReplyMessagesQuery final : public Td::ResultHandler { } }; +class QuickReplyManager::SendQuickReplyMessageQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + + public: + void send(const QuickReplyMessage *m) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + + int32 flags = telegram_api::messages_sendMessage::QUICK_REPLY_SHORTCUT_MASK; + if (m->disable_web_page_preview) { + flags |= telegram_api::messages_sendMessage::NO_WEBPAGE_MASK; + } + if (m->invert_media) { + flags |= telegram_api::messages_sendMessage::INVERT_MEDIA_MASK; + } + auto reply_to = + MessageInputReplyTo(m->reply_to_message_id, DialogId(), Auto(), 0).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; + } + const FormattedText *message_text = get_message_content_text(m->content.get()); + CHECK(message_text != nullptr); + auto entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendQuickReplyMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMessage::ENTITIES_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendMessage( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, telegram_api::make_object(), + std::move(reply_to), message_text->text, m->random_id, nullptr, std::move(entities), 0, nullptr, + td_->quick_reply_manager_->get_input_quick_reply_shortcut(m->shortcut_id)), + {{"me"}})); + } + + 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()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendQuickReplyMessageQuery for " << random_id_ << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), {random_id_}); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendQuickReplyMessageQuery: " << status; + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, {random_id_}, std::move(status)); + } +}; + +class QuickReplyManager::SendQuickReplyMediaQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + FileId file_id_; + FileId thumbnail_file_id_; + string file_reference_; + bool was_uploaded_ = false; + bool was_thumbnail_uploaded_ = false; + + public: + void send(FileId file_id, FileId thumbnail_file_id, const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + file_id_ = file_id; + thumbnail_file_id_ = thumbnail_file_id; + file_reference_ = FileManager::extract_file_reference(input_media); + was_uploaded_ = FileManager::extract_was_uploaded(input_media); + was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); + + int32 flags = telegram_api::messages_sendMedia::QUICK_REPLY_SHORTCUT_MASK; + if (m->invert_media) { + flags |= telegram_api::messages_sendMedia::INVERT_MEDIA_MASK; + } + auto reply_to = + MessageInputReplyTo(m->reply_to_message_id, DialogId(), Auto(), 0).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMedia::REPLY_TO_MASK; + } + vector> entities; + const FormattedText *message_text = get_message_content_text(m->content.get()); + if (message_text != nullptr) { + entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendQuickReplyMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; + } + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendMedia( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, telegram_api::make_object(), std::move(reply_to), + std::move(input_media), message_text == nullptr ? string() : message_text->text, m->random_id, nullptr, + std::move(entities), 0, nullptr, td_->quick_reply_manager_->get_input_quick_reply_shortcut(m->shortcut_id)), + {{"me"}})); + } + + 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()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendQuickReplyMediaQuery for " << random_id_ << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), {random_id_}); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendQuickReplyMediaQuery: " << status; + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, {random_id_}, std::move(status)); + } +}; + QuickReplyManager::QuickReplyMessage::~QuickReplyMessage() = default; template @@ -375,7 +501,7 @@ void QuickReplyManager::Shortcut::store(StorerT &storer) const { for (const auto &message : messages_) { if (message->message_id.is_server()) { server_total_count++; - } else if (message->message_id.is_local()) { + } else { local_total_count++; } } @@ -397,9 +523,7 @@ void QuickReplyManager::Shortcut::store(StorerT &storer) const { td::store(local_total_count, storer); } for (const auto &message : messages_) { - if (message->message_id.is_server() || message->message_id.is_local()) { - td::store(message, storer); - } + td::store(message, storer); } } @@ -1186,6 +1310,204 @@ void QuickReplyManager::delete_quick_reply_messages_on_server(QuickReplyShortcut td_->create_handler(std::move(promise))->send(shortcut_id, message_ids); } +telegram_api::object_ptr QuickReplyManager::get_input_quick_reply_shortcut( + QuickReplyShortcutId shortcut_id) const { + if (shortcut_id.is_server()) { + return telegram_api::make_object(shortcut_id.get()); + } + const auto *s = get_shortcut(shortcut_id); + CHECK(s != nullptr); + return telegram_api::make_object(s->name_); +} + +bool QuickReplyManager::check_send_quick_reply_messages_response( + QuickReplyShortcutId shortcut_id, const telegram_api::object_ptr &updates_ptr, + const vector &random_ids) { + if (updates_ptr->get_id() != telegram_api::updates::ID) { + return false; + } + const auto &updates = static_cast(updates_ptr.get())->updates_; + FlatHashSet sent_random_ids; + for (auto &update : updates) { + if (update->get_id() == telegram_api::updateMessageID::ID) { + auto update_message_id = static_cast(update.get()); + int64 random_id = update_message_id->random_id_; + if (random_id == 0) { + return false; + } + if (!sent_random_ids.insert(random_id).second) { + return false; + } + } + } + if (sent_random_ids.size() != random_ids.size()) { + return false; + } + for (auto random_id : random_ids) { + if (sent_random_ids.count(random_id) != 1) { + return false; + } + } + int32 new_shortcut_count = 0; + for (auto &update : updates) { + if (update->get_id() == telegram_api::updateNewQuickReply::ID) { + if (!QuickReplyShortcutId( + static_cast(update.get())->quick_reply_->shortcut_id_) + .is_server()) { + return false; + } + new_shortcut_count++; + } + } + if (new_shortcut_count != (shortcut_id.is_server() ? 0 : 1)) { + return false; + } + return true; +} + +void QuickReplyManager::process_send_quick_reply_updates(QuickReplyShortcutId shortcut_id, + telegram_api::object_ptr updates_ptr, + vector random_ids) { + if (!check_send_quick_reply_messages_response(shortcut_id, updates_ptr, random_ids)) { + LOG(ERROR) << "Receive " << to_string(updates_ptr); + on_failed_send_quick_reply_messages(shortcut_id, std::move(random_ids), + Status::Error(500, "Receive wrong response")); + return; + } + + auto updates = telegram_api::move_object_as(updates_ptr); + td_->user_manager_->on_get_users(std::move(updates->users_), "process_send_quick_reply_updates"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "process_send_quick_reply_updates"); + + bool is_shortcut_new = !shortcut_id.is_server(); + { + auto it = get_shortcut_it(shortcut_id); + if (it == shortcuts_.shortcuts_.end()) { + // the shortcut was deleted + reload_quick_reply_shortcuts(); + return; + } + + if (is_shortcut_new) { + QuickReplyShortcutId new_shortcut_id; + for (auto &update : updates->updates_) { + if (update->get_id() == telegram_api::updateNewQuickReply::ID) { + new_shortcut_id = QuickReplyShortcutId( + static_cast(update.get())->quick_reply_->shortcut_id_); + update = nullptr; + } + } + CHECK(new_shortcut_id.is_server()); + send_update_quick_reply_shortcut_deleted(it->get()); + (*it)->shortcut_id_ = new_shortcut_id; + for (auto &message : (*it)->messages_) { + CHECK(message->shortcut_id == shortcut_id); + message->shortcut_id = new_shortcut_id; + } + shortcut_id = new_shortcut_id; + } + } + auto *s = get_shortcut(shortcut_id); + CHECK(s != nullptr); + + for (auto &random_id : random_ids) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + if ((*it)->random_id == random_id) { + MessageId new_message_id; + for (auto &update : updates->updates_) { + if (update != nullptr && update->get_id() == telegram_api::updateMessageID::ID && + static_cast(update.get())->random_id_ == random_id) { + new_message_id = + MessageId(ServerMessageId(static_cast(update.get())->id_)); + update = nullptr; + } + } + if (new_message_id.is_valid()) { + for (auto &update : updates->updates_) { + if (update != nullptr && update->get_id() == telegram_api::updateQuickReplyMessage::ID && + MessageId::get_message_id( + static_cast(update.get())->message_, false) == + new_message_id) { + auto message = create_message( + std::move(static_cast(update.get())->message_), + "process_send_quick_reply_updates"); + if (message != nullptr && message->shortcut_id == shortcut_id) { + change_message_files({shortcut_id, message->message_id}, message.get(), {}); + *it = std::move(message); + s->server_total_count_++; + s->local_total_count_--; + } + update = nullptr; + } + } + } + + break; + } + } + } + + sort_quick_reply_messages(s->messages_); + send_update_quick_reply_shortcut(s, "process_send_quick_reply_updates"); + send_update_quick_reply_shortcut_messages(s, "process_send_quick_reply_updates"); + if (is_shortcut_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); +} + +void QuickReplyManager::on_failed_send_quick_reply_messages(QuickReplyShortcutId shortcut_id, vector random_ids, + Status error) { + // TODO +} + +Result> QuickReplyManager::send_message( + const string &shortcut_name, MessageId reply_to_message_id, + td_api::object_ptr &&input_message_content) { + TRY_RESULT(message_content, process_input_message_content(std::move(input_message_content))); + TRY_RESULT(s, create_new_local_shortcut(shortcut_name, 1)); + bool is_new = s->messages_.empty(); + reply_to_message_id = get_input_reply_to_message_id(s, reply_to_message_id); + + auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), message_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + auto *m = add_local_message(s, reply_to_message_id, std::move(content), message_content.invert_media, + message_content.via_bot_user_id, false, message_content.disable_web_page_preview, + std::move(message_content.emoji)); + + send_update_quick_reply_shortcut(s, "send_message"); + send_update_quick_reply_shortcut_messages(s, "send_message"); + if (is_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); + + do_send_message(m); + + return get_quick_reply_message_object(m, "send_message"); +} + +void QuickReplyManager::do_send_message(const QuickReplyMessage *m, vector bad_parts) { + bool is_edit = m->message_id.is_server(); + LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << QuickReplyMessageFullId(m->shortcut_id, m->message_id); + + auto content = m->content.get(); + CHECK(content != nullptr); + auto content_type = content->get_type(); + if (content_type == MessageContentType::Text) { + CHECK(!is_edit); + const FormattedText *message_text = get_message_content_text(content); + CHECK(message_text != nullptr); + auto input_media = get_message_content_input_media_web_page(td_, content); + if (input_media == nullptr) { + td_->create_handler()->send(m); + } else { + td_->create_handler()->send(FileId(), FileId(), m, std::move(input_media)); + } + return; + } +} + void QuickReplyManager::get_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, Promise &&promise) { load_quick_reply_shortcuts(); auto *s = get_shortcut(shortcut_id); @@ -1768,11 +2090,16 @@ void QuickReplyManager::load_quick_reply_shortcuts() { } shortcuts_.are_inited_ = true; - for (const auto &shortcut : shortcuts_.shortcuts_) { + for (auto &shortcut : shortcuts_.shortcuts_) { if (shortcut->shortcut_id_.get() >= next_local_shortcut_id_) { next_local_shortcut_id_ = shortcut->shortcut_id_.get() + 1; } - for (const auto &message : shortcut->messages_) { + for (auto &message : shortcut->messages_) { + if (message->shortcut_id != shortcut->shortcut_id_) { + LOG(ERROR) << "Receive quick reply " << message->message_id << " in " << message->shortcut_id << " instead of " + << shortcut->shortcut_id_; + message->shortcut_id = shortcut->shortcut_id_; + } change_message_files({shortcut->shortcut_id_, message->message_id}, message.get(), {}); if (message->message_id.is_server()) { @@ -1780,6 +2107,8 @@ void QuickReplyManager::load_quick_reply_shortcuts() { (message->legacy_layer != 0 && message->legacy_layer < MTPROTO_LAYER)) { reload_quick_reply_message(shortcut->shortcut_id_, message->message_id, Promise()); } + } else if (message->message_id.is_yet_unsent()) { + do_send_message(message.get()); } } send_update_quick_reply_shortcut(shortcut.get(), "load_quick_reply_shortcuts"); diff --git a/td/telegram/QuickReplyManager.h b/td/telegram/QuickReplyManager.h index 227a22cd1..5ed66ae82 100644 --- a/td/telegram/QuickReplyManager.h +++ b/td/telegram/QuickReplyManager.h @@ -58,6 +58,10 @@ class QuickReplyManager final : public Actor { void delete_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, const vector &message_ids, Promise &&promise); + Result> send_message( + const string &shortcut_name, MessageId reply_to_message_id, + td_api::object_ptr &&input_message_content); + void reload_quick_reply_shortcuts(); void reload_quick_reply_messages(QuickReplyShortcutId shortcut_id, Promise &&promise); @@ -164,6 +168,9 @@ class QuickReplyManager final : public Actor { void parse(ParserT &parser); }; + class SendQuickReplyMessageQuery; + class SendQuickReplyMediaQuery; + void tear_down() final; static bool is_shortcut_name_letter(uint32 code); @@ -296,6 +303,21 @@ class QuickReplyManager final : public Actor { void delete_quick_reply_messages_on_server(QuickReplyShortcutId shortcut_id, const vector &message_ids, Promise &&promise); + telegram_api::object_ptr get_input_quick_reply_shortcut( + QuickReplyShortcutId shortcut_id) const; + + bool check_send_quick_reply_messages_response(QuickReplyShortcutId shortcut_id, + const telegram_api::object_ptr &updates_ptr, + const vector &random_ids); + + void process_send_quick_reply_updates(QuickReplyShortcutId shortcut_id, + telegram_api::object_ptr updates_ptr, + vector random_ids); + + void on_failed_send_quick_reply_messages(QuickReplyShortcutId shortcut_id, vector random_ids, Status error); + + void do_send_message(const QuickReplyMessage *m, vector bad_parts = {}); + string get_quick_reply_shortcuts_database_key(); void save_quick_reply_shortcuts(); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 8218e14f6..c37444aa6 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5878,6 +5878,17 @@ void Td::on_request(uint64 id, const td_api::deleteQuickReplyShortcutMessages &r QuickReplyShortcutId(request.shortcut_id_), MessageId::get_message_ids(request.message_ids_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::addQuickReplyShortcutMessage &request) { + CLEAN_INPUT_STRING(request.shortcut_name_); + auto r_sent_message = quick_reply_manager_->send_message( + request.shortcut_name_, MessageId(request.reply_to_message_id_), std::move(request.input_message_content_)); + if (r_sent_message.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, r_sent_message.move_as_ok()); + } +} + void Td::on_request(uint64 id, const td_api::getStory &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 9146c4423..634a0ab47 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -899,6 +899,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::deleteQuickReplyShortcutMessages &request); + void on_request(uint64 id, td_api::addQuickReplyShortcutMessage &request); + void on_request(uint64 id, const td_api::getStory &request); void on_request(uint64 id, const td_api::getChatsToSendStories &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 8678553cc..ff5eef3f2 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2364,6 +2364,11 @@ class CliClient final : public Actor { std::move(input_message_content))); return; } + if (!quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + quick_reply_shortcut_name_, reply_message_id_, std::move(input_message_content))); + return; + } auto id = send_request(td_api::make_object( chat_id, message_thread_id_, get_input_message_reply_to(), td_api::make_object(disable_notification, from_background, true, true, @@ -4771,6 +4776,8 @@ class CliClient final : public Actor { link_preview_force_large_media_, link_preview_show_above_text_); } else if (op == "ssmt") { saved_messages_topic_id_ = as_chat_id(args); + } else if (op == "sqrs") { + quick_reply_shortcut_name_ = args; } else if (op == "sm" || op == "sms" || op == "smf") { ChatId chat_id; string message; @@ -6867,6 +6874,7 @@ class CliClient final : public Actor { bool link_preview_force_large_media_ = false; bool link_preview_show_above_text_ = false; int64 saved_messages_topic_id_ = 0; + string quick_reply_shortcut_name_; ConcurrentScheduler *scheduler_{nullptr};