From 516f9cb9ce189dd636c18a0490d91a79f7803e64 Mon Sep 17 00:00:00 2001 From: levlam Date: Thu, 21 Jan 2021 22:00:08 +0300 Subject: [PATCH] Add importMessages method. --- td/generate/scheme/td_api.tl | 7 + td/generate/scheme/td_api.tlo | Bin 194860 -> 195048 bytes td/telegram/MessagesManager.cpp | 248 ++++++++++++++++++++++++++++++++ td/telegram/MessagesManager.h | 32 ++++- td/telegram/Td.cpp | 7 + td/telegram/Td.h | 2 + td/telegram/cli.cpp | 15 +- 7 files changed, 307 insertions(+), 4 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 808e89c3f..01bdf2329 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -4385,6 +4385,13 @@ readFilePart file_id:int32 offset:int32 count:int32 = FilePart; deleteFile file_id:int32 = Ok; +//@description Imports messages exported from another app +//@chat_id Identifier of a chat to which the messages will be imported. It must be an identifier of a private chat with a mutual contact or an identifier of a created supergroup chat +//@message_file File with messages to import. Only inputFileLocal and inputFileGenerated are supported. The file must not be previously uploaded +//@attached_files Files used in the imported messages. Only inputFileLocal and inputFileGenerated are supported. The files must not be previously uploaded +importMessages chat_id:int53 message_file:InputFile attached_files:vector = Ok; + + //@description Replaces current permanent invite link for a chat with a new permanent invite link. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right @chat_id Chat identifier replacePermanentChatInviteLink chat_id:int53 = ChatInviteLink; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 96ff5dba67d5a3bc9908663a8b211b1d2c1c862e..8539abddbbd20ca0bb5766bc02a2e049e53eacc7 100644 GIT binary patch delta 98 zcmZ4UiTlN8?uHh|EleU>jI+0kYBALm30-|WfiE++Ait=@H?_DpF+H_-I=>H-ArDV3 xgd3lhnKRkvwu}H@Vo6D2az<(jNF=qGVRC?*%;XKXMW%nyViMRsVGh%r7ywk5CA|Ou delta 27 jcmaFynS0GA?uHh|EleU>jI*|jYBALmZTFqaG&=?WqHPMK diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 7031c9b93..837a8f4c4 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -1009,6 +1009,92 @@ class CreateChannelQuery : public Td::ResultHandler { } }; +class InitHistoryImportQuery : public Td::ResultHandler { + Promise promise_; + FileId file_id_; + DialogId dialog_id_; + vector attached_file_ids_; + + public: + explicit InitHistoryImportQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_file, + vector attached_file_ids) { + CHECK(input_file != nullptr); + file_id_ = file_id; + dialog_id_ = dialog_id; + attached_file_ids_ = std::move(attached_file_ids); + attached_file_ids_.clear(); + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create(telegram_api::messages_initHistoryImport( + std::move(input_peer), std::move(input_file), narrow_cast(attached_file_ids_.size())))); + } + + 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()); + } + + td->file_manager_->delete_partial_remote_location(file_id_); + + auto ptr = result_ptr.move_as_ok(); + td->messages_manager_->start_import_messages(dialog_id_, ptr->id_, std::move(attached_file_ids_), + std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + td->file_manager_->delete_partial_remote_location(file_id_); + if (!td->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { + LOG(ERROR) << "Receive file reference error " << status; + } + + // TODO support FILE_PART_*_MISSING + + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "InitHistoryImportQuery"); + promise_.set_error(std::move(status)); + } +}; + +class StartImportHistoryQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit StartImportHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, int64 import_id) { + dialog_id_ = dialog_id; + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + + send_query( + G()->net_query_creator().create(telegram_api::messages_startHistoryImport(std::move(input_peer), import_id))); + } + + 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()); + } + + if (!result_ptr.ok()) { + return on_error(id, Status::Error(500, "Import history returned false")); + } + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartImportHistoryQuery"); + promise_.set_error(std::move(status)); + } +}; + class EditDialogPhotoQuery : public Td::ResultHandler { Promise promise_; FileId file_id_; @@ -4579,6 +4665,24 @@ class MessagesManager::UploadDialogPhotoCallback : public FileManager::UploadCal } }; +class MessagesManager::UploadImportedMessagesCallback : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, tl_object_ptr input_file) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages_error, file_id, + std::move(error)); + } +}; + template void MessagesManager::Message::store(StorerT &storer) const { using td::store; @@ -5513,6 +5617,7 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent upload_media_callback_ = std::make_shared(); upload_thumbnail_callback_ = std::make_shared(); upload_dialog_photo_callback_ = std::make_shared(); + upload_imported_messages_callback_ = std::make_shared(); channel_get_difference_timeout_.set_callback(on_channel_get_difference_timeout_callback); channel_get_difference_timeout_.set_callback_data(static_cast(this)); @@ -8555,6 +8660,70 @@ void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status promise.set_error(std::move(status)); } +void MessagesManager::on_upload_imported_messages(FileId file_id, tl_object_ptr input_file) { + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_imported_messages_.find(file_id); + if (it == being_uploaded_imported_messages_.end()) { + // just in case, as in on_upload_media + return; + } + + CHECK(it->second != nullptr); + DialogId dialog_id = it->second->dialog_id; + vector attached_file_ids = std::move(it->second->attached_file_ids); + bool is_reupload = it->second->is_reupload; + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_messages_.erase(it); + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + FileView file_view = td_->file_manager_->get_file_view(file_id); + CHECK(!file_view.is_encrypted()); + if (input_file == nullptr && file_view.has_remote_location()) { + if (file_view.main_remote_location().is_web()) { + return promise.set_error(Status::Error(400, "Can't use web photo")); + } + if (is_reupload) { + return promise.set_error(Status::Error(400, "Failed to reupload the file")); + } + + CHECK(file_view.get_type() == FileType::Document); + // delete file reference and forcely reupload the file + auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); + td_->file_manager_->delete_file_reference(file_id, file_reference); + upload_imported_messages(dialog_id, file_id, std::move(attached_file_ids), true, std::move(promise), {-1}); + return; + } + CHECK(input_file != nullptr); + + td_->create_handler(std::move(promise)) + ->send(dialog_id, file_id, std::move(input_file), std::move(attached_file_ids)); +} + +void MessagesManager::on_upload_imported_messages_error(FileId file_id, Status status) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(INFO) << "File " << file_id << " has upload error " << status; + CHECK(status.is_error()); + + auto it = being_uploaded_imported_messages_.find(file_id); + if (it == being_uploaded_imported_messages_.end()) { + // just in case, as in on_upload_media_error + return; + } + + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_messages_.erase(it); + + promise.set_error(std::move(status)); +} + void MessagesManager::before_get_difference() { running_get_difference_ = true; @@ -26094,6 +26263,85 @@ Result MessagesManager::add_local_message( return message_id; } +void MessagesManager::import_messages(DialogId dialog_id, const td_api::object_ptr &message_file, + const vector> &attached_files, + Promise &&promise) { + if (!have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + switch (dialog_id.get_type()) { + case DialogType::User: + if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) { + return promise.set_error(Status::Error(400, "User must be a mutual contact")); + } + break; + case DialogType::Chat: + return promise.set_error(Status::Error(400, "Basic groups must be updagraded to supergroups first")); + case DialogType::Channel: + if (is_broadcast_channel(dialog_id)) { + return promise.set_error(Status::Error(400, "Can't import messages to channels")); + } + if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to import messages")); + } + break; + case DialogType::SecretChat: + return promise.set_error(Status::Error(400, "Can't import messages to secret chats")); + case DialogType::None: + default: + UNREACHABLE(); + } + + auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, message_file, dialog_id, false, false); + if (r_file_id.is_error()) { + // TODO TRY_RESULT_PROMISE(promise, ...); + return promise.set_error(Status::Error(400, r_file_id.error().message())); + } + FileId file_id = r_file_id.ok(); + + vector attached_file_ids; + attached_file_ids.reserve(attached_files.size()); + for (auto &attached_file : attached_files) { + auto r_attached_file_id = + td_->file_manager_->get_input_file_id(FileType::Document, attached_file, dialog_id, false, false); + if (r_attached_file_id.is_error()) { + // TODO TRY_RESULT_PROMISE(promise, ...); + return promise.set_error(Status::Error(400, r_attached_file_id.error().message())); + } + attached_file_ids.push_back(r_attached_file_id.ok()); + } + + upload_imported_messages(dialog_id, td_->file_manager_->dup_file_id(file_id), std::move(attached_file_ids), false, + std::move(promise)); +} + +void MessagesManager::upload_imported_messages(DialogId dialog_id, FileId file_id, vector attached_file_ids, + bool is_reupload, Promise &&promise, vector bad_parts) { + CHECK(file_id.is_valid()); + LOG(INFO) << "Ask to upload imported messages file " << file_id; + CHECK(being_uploaded_imported_messages_.find(file_id) == being_uploaded_imported_messages_.end()); + being_uploaded_imported_messages_.emplace( + file_id, td::make_unique(dialog_id, std::move(attached_file_ids), is_reupload, + std::move(promise))); + // TODO use force_reupload if is_reupload + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_messages_callback_, 1, 0); +} + +void MessagesManager::start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, + Promise &&promise) { + CHECK(attached_file_ids.empty()); + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + td_->create_handler(std::move(promise))->send(dialog_id, import_id); +} + bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) { if (!new_message_id.is_valid() || !new_message_id.is_server()) { LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from " diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index cc3be9d85..16b94d513 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -394,6 +394,12 @@ class MessagesManager : public Actor { tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; + void import_messages(DialogId dialog_id, const td_api::object_ptr &message_file, + const vector> &attached_files, Promise &&promise); + + void start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, + Promise &&promise); + void edit_message_text(FullMessageId full_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise); @@ -890,6 +896,9 @@ class MessagesManager : public Actor { void upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation, double main_frame_timestamp, bool is_reupload, Promise &&promise, vector bad_parts = {}); + void upload_imported_messages(DialogId dialog_id, FileId file_id, vector attached_file_ids, bool is_reupload, + Promise &&promise, vector bad_parts = {}); + void on_binlog_events(vector &&events); void set_poll_answer(FullMessageId full_message_id, vector &&option_ids, Promise &&promise); @@ -2824,6 +2833,9 @@ class MessagesManager : public Actor { tl_object_ptr &&input_chat_photo, Promise &&promise); + void on_upload_imported_messages(FileId file_id, tl_object_ptr input_file); + void on_upload_imported_messages_error(FileId file_id, Status status); + void add_sponsored_dialog(const Dialog *d, DialogSource source); void save_sponsored_dialog(); @@ -2956,10 +2968,12 @@ class MessagesManager : public Actor { class UploadMediaCallback; class UploadThumbnailCallback; class UploadDialogPhotoCallback; + class UploadImportedMessagesCallback; std::shared_ptr upload_media_callback_; std::shared_ptr upload_thumbnail_callback_; std::shared_ptr upload_dialog_photo_callback_; + std::shared_ptr upload_imported_messages_callback_; double last_channel_pts_jump_warning_time_ = 0; @@ -3040,7 +3054,23 @@ class MessagesManager : public Actor { , promise(std::move(promise)) { } }; - std::unordered_map being_uploaded_dialog_photos_; // file_id -> ... + std::unordered_map being_uploaded_dialog_photos_; + + struct UploadedImportedMessagesInfo { + DialogId dialog_id; + vector attached_file_ids; + bool is_reupload; + Promise promise; + + UploadedImportedMessagesInfo(DialogId dialog_id, vector &&attached_file_ids, bool is_reupload, + Promise &&promise) + : dialog_id(dialog_id) + , attached_file_ids(std::move(attached_file_ids)) + , is_reupload(is_reupload) + , promise(std::move(promise)) { + } + }; + std::unordered_map, FileIdHash> being_uploaded_imported_messages_; struct PendingMessageGroupSend { DialogId dialog_id; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 97a6e6244..eee7d2cbc 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6535,6 +6535,13 @@ void Td::on_request(uint64 id, const td_api::deleteFile &request) { "td_api::deleteFile"); } +void Td::on_request(uint64 id, const td_api::importMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->import_messages(DialogId(request.chat_id_), request.message_file_, request.attached_files_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 7aadd9fc7..4607683de 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -836,6 +836,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::deleteFile &request); + void on_request(uint64 id, const td_api::importMessages &request); + void on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request); void on_request(uint64 id, const td_api::getBlockedMessageSenders &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index ec9a5abe0..ad955338e 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2968,6 +2968,15 @@ class CliClient final : public Actor { as_input_file(document), nullptr, true, as_caption("")); return content; }))); + } else if (op == "im") { + string chat_id; + string message_file; + vector attached_files; + get_args(args, chat_id, message_file, args); + attached_files = full_split(args); + send_request(td_api::make_object( + as_chat_id(chat_id), as_input_file(message_file), + transform(attached_files, [](const string &attached_file) { return as_input_file(attached_file); }))); } else if (op == "em") { string chat_id; string message_id; @@ -3412,11 +3421,11 @@ class CliClient final : public Actor { string title; get_args(args, user_ids_string, title); send_request(td_api::make_object(as_user_ids(user_ids_string), title)); - } else if (op == "cnch") { + } else if (op == "cnchc") { send_request(td_api::make_object(args, true, "Description", nullptr)); - } else if (op == "cnsg") { + } else if (op == "cnsgc") { send_request(td_api::make_object(args, false, "Description", nullptr)); - } else if (op == "cngc") { + } else if (op == "cnsgcloc") { send_request(td_api::make_object( args, false, "Description", td_api::make_object(as_location("40.0", "60.0"), "address")));