diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index ab7a85a8c..8fecaf881 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -3110,6 +3110,11 @@ sendInlineQueryResultMessage chat_id:int53 reply_to_message_id:int53 disable_not //@remove_caption True, if media captions of message copies needs to be removed. Ignored if send_copy is false forwardMessages chat_id:int53 from_chat_id:int53 message_ids:vector disable_notification:Bool from_background:Bool as_album:Bool send_copy:Bool remove_caption:Bool = Messages; +//@description Resends failed to send messages. Can be called only for failed to send messages for which messageSendingStateFailed.can_retry is true and after specified in messageSendingStateFailed.retry_after time passed. +//-If a message is re-sent, the corresponding failed to send message is deleted. Returns the sent messages in the same order as the message identifiers passed in message_ids. If a message can't be re-sent, null will be returned instead of the message +//@chat_id Identifier of the chat to send messages @message_ids Identifiers of the messages to resend. Message identifiers must be in a strictly increasing order +resendMessages chat_id:int53 message_ids:vector = Messages; + //@description Changes the current TTL setting (sets a new self-destruct timer) in a secret chat and sends the corresponding message @chat_id Chat identifier @ttl New TTL value, in seconds sendChatSetTtlMessage chat_id:int53 ttl:int32 = Message; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index afe5d07ec..889696490 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 90ec35df7..cca223c60 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -15552,7 +15552,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial tl_object_ptr sending_state; if (m->is_failed_to_send) { sending_state = make_tl_object( - m->send_error_code, m->send_error_message, m->send_error_code == 429, max(m->try_resend_at - Time::now(), 0.0)); + m->send_error_code, m->send_error_message, can_resend_message(m), max(m->try_resend_at - Time::now(), 0.0)); } else if (m->message_id.is_yet_unsent()) { sending_state = make_tl_object(); } @@ -16253,15 +16253,15 @@ Result MessagesManager::send_message(DialogId dialog_id, MessageId re update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos); } + save_send_message_logevent(dialog_id, m); + do_send_message(dialog_id, m); + send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_message"); } - auto message_id = m->message_id; - save_send_message_logevent(dialog_id, m); - do_send_message(dialog_id, m); - return message_id; + return m->message_id; } Result MessagesManager::process_input_message_content( @@ -16338,7 +16338,7 @@ Result> MessagesManager::send_message_group( return Status::Error(4, "Too much messages to send as an album"); } if (input_message_contents.empty()) { - return Status::Error(4, "There is no messages to send"); + return Status::Error(4, "There are no messages to send"); } Dialog *d = get_dialog_force(dialog_id); @@ -16394,7 +16394,7 @@ Result> MessagesManager::send_message_group( return result; } -void MessagesManager::save_send_message_logevent(DialogId dialog_id, Message *m) { +void MessagesManager::save_send_message_logevent(DialogId dialog_id, const Message *m) { if (!G()->parameters().use_message_db) { return; } @@ -16407,7 +16407,7 @@ void MessagesManager::save_send_message_logevent(DialogId dialog_id, Message *m) m->send_message_logevent_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendMessage, storer); } -void MessagesManager::do_send_message(DialogId dialog_id, Message *m, vector bad_parts) { +void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vector bad_parts) { bool is_edit = m->message_id.is_server(); LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << FullMessageId(dialog_id, m->message_id); bool is_secret = dialog_id.get_type() == DialogType::SecretChat; @@ -16484,7 +16484,7 @@ void MessagesManager::do_send_message(DialogId dialog_id, Message *m, vector &&input_media, FileId file_id, FileId thumbnail_file_id) { CHECK(m != nullptr); @@ -16563,7 +16563,7 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, Message *m, } } -void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, Message *m, +void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, const Message *m, SecretInputMedia &&secret_input_media, FileId file_id, FileId thumbnail_file_id) { CHECK(m != nullptr); @@ -17016,7 +17016,7 @@ class MessagesManager::SendBotStartMessageLogEvent { }; void MessagesManager::save_send_bot_start_message_logevent(UserId bot_user_id, DialogId dialog_id, - const string ¶meter, Message *m) { + const string ¶meter, const Message *m) { if (!G()->parameters().use_message_db) { return; } @@ -17035,7 +17035,7 @@ void MessagesManager::save_send_bot_start_message_logevent(UserId bot_user_id, D } void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter, - Message *m) { + const Message *m) { LOG(INFO) << "Do send bot start " << FullMessageId(dialog_id, m->message_id) << " to bot " << bot_user_id; int64 random_id = begin_send_message(dialog_id, m); @@ -17118,9 +17118,8 @@ Result MessagesManager::send_inline_query_result_message(DialogId dia if (to_secret) { save_send_message_logevent(dialog_id, m); - auto message_id = m->message_id; do_send_message(dialog_id, m); - return message_id; + return m->message_id; } save_send_inline_query_result_message_logevent(dialog_id, m, query_id, result_id); @@ -17155,8 +17154,8 @@ class MessagesManager::SendInlineQueryResultMessageLogEvent { } }; -void MessagesManager::save_send_inline_query_result_message_logevent(DialogId dialog_id, Message *m, int64 query_id, - const string &result_id) { +void MessagesManager::save_send_inline_query_result_message_logevent(DialogId dialog_id, const Message *m, + int64 query_id, const string &result_id) { if (!G()->parameters().use_message_db) { return; } @@ -17174,7 +17173,7 @@ void MessagesManager::save_send_inline_query_result_message_logevent(DialogId di binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendInlineQueryResultMessage, storer); } -void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, Message *m, int64 query_id, +void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, const Message *m, int64 query_id, const string &result_id) { LOG(INFO) << "Do send inline query result " << FullMessageId(dialog_id, m->message_id); @@ -17337,6 +17336,33 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo return false; } +bool MessagesManager::can_resend_message(const Message *m) { + if (m->send_error_code != 429) { + return false; + } + if (m->is_bot_start_message) { + return false; + } + if (m->forward_info != nullptr || m->real_forward_from_dialog_id.is_valid()) { + // TODO implement resending of forwarded messages + return false; + } + if (m->via_bot_user_id.is_valid() || m->hide_via_bot) { + // via bot message + if (!get_message_content_game_bot_user_id(m->content.get()).is_valid()) { + // TODO implement resending via_bot messages other than games + return false; + } + } + + auto content_type = m->content->get_type(); + if (content_type == MessageContentType::ChatSetTtl || content_type == MessageContentType::ScreenshotTaken) { + // TODO implement resending of ChatSetTtl and ScreenshotTaken messages + return false; + } + return true; +} + bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const { if (dialog_id.get_type() != DialogType::Channel) { return false; @@ -18407,7 +18433,7 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i return Status::Error(4, "Too much messages to forward"); } if (message_ids.empty()) { - return Status::Error(4, "There is no messages to forward"); + return Status::Error(4, "There are no messages to forward"); } Dialog *from_dialog = get_dialog_force(from_dialog_id); @@ -18480,7 +18506,7 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i auto content_type = content->get_type(); bool is_game = content_type == MessageContentType::Game; if (need_copy) { - if (is_game) { + if (is_game && !get_message_content_game_bot_user_id(content.get()).is_valid()) { LOG(INFO) << "Can't copy game from " << message_id; continue; } @@ -18642,6 +18668,118 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i return result; } +Result> MessagesManager::resend_messages(DialogId dialog_id, vector message_ids) { + if (message_ids.empty()) { + return Status::Error(4, "There are no messages to resend"); + } + + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return Status::Error(400, "Chat not found"); + } + + TRY_STATUS(can_send_message(dialog_id)); + + MessageId last_message_id; + for (auto &message_id : message_ids) { + message_id = get_persistent_message_id(d, message_id); + const Message *m = get_message_force(d, message_id, "resend_messages"); + if (m == nullptr) { + return Status::Error(400, "Message not found"); + } + if (!m->is_failed_to_send) { + return Status::Error(400, "Message is not failed to send"); + } + if (!can_resend_message(m)) { + return Status::Error(400, "Message can't be re-sent"); + } + if (m->try_resend_at < Time::now()) { + return Status::Error(400, "Message can't be re-sent yet"); + } + if (m->message_id.get() <= last_message_id.get()) { + return Status::Error(400, "Message identifiers must be in a strictly increasing order"); + } + last_message_id = message_id; + } + + vector> new_contents(message_ids.size()); + std::unordered_map> new_media_album_ids; + for (size_t i = 0; i < message_ids.size(); i++) { + MessageId message_id = message_ids[i]; + const Message *m = get_message(d, message_id); + CHECK(m != nullptr); + + unique_ptr content = dup_message_content(td_, dialog_id, m->content.get(), false); + if (content == nullptr) { + LOG(INFO) << "Can't resend " << message_id; + continue; + } + + auto can_send_status = can_send_message_content(dialog_id, content.get(), false); + if (can_send_status.is_error()) { + LOG(INFO) << "Can't resend " << message_id << ": " << can_send_status.message(); + continue; + } + + if (!get_message_content_game_bot_user_id(content.get()).is_valid()) { + // must not happen + LOG(ERROR) << "Can't resend game from " << message_id; + continue; + } + + new_contents[i] = std::move(content); + + if (m->media_album_id != 0) { + auto &new_media_album_id = new_media_album_ids[m->media_album_id]; + new_media_album_id.second++; + if (new_media_album_id.second == 2) { // have at least 2 messages in the new album + CHECK(new_media_album_id.first == 0); + new_media_album_id.first = generate_new_media_album_id(); + } + if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) { + CHECK(new_media_album_id.first != 0); + new_media_album_id.first = 0; // just in case + } + } + } + + vector result(message_ids.size()); + bool need_update_dialog_pos = false; + for (size_t i = 0; i < message_ids.size(); i++) { + if (new_contents[i] == nullptr) { + continue; + } + + unique_ptr message = delete_message(d, message_ids[i], true, &need_update_dialog_pos, "resend_messages"); + CHECK(message != nullptr); + send_update_delete_messages(dialog_id, {message->message_id.get()}, true, false); + + Message *m = + get_message_to_send(d, get_reply_to_message_id(d, message->reply_to_message_id), message->disable_notification, + message->from_background, std::move(new_contents[i]), &need_update_dialog_pos); + m->reply_markup = std::move(message->reply_markup); + m->via_bot_user_id = message->via_bot_user_id; + m->disable_web_page_preview = message->disable_web_page_preview; + m->clear_draft = false; // never clear draft in resend + m->ttl = message->ttl; + m->is_content_secret = message->is_content_secret; + m->media_album_id = new_media_album_ids[message->media_album_id].first; + + save_send_message_logevent(dialog_id, m); + do_send_message(dialog_id, m); + + send_update_new_message(d, m); + + result[i] = m->message_id; + } + + if (need_update_dialog_pos) { + send_update_chat_last_message(d, "resend_messages"); + } + + return result; +} + Result MessagesManager::send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl) { if (dialog_id.get_type() != DialogType::SecretChat) { return Status::Error(5, "Can't set chat ttl in non-secret chat"); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index a97140c7d..7032b43a5 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -351,6 +351,8 @@ class MessagesManager : public Actor { bool from_background, bool in_game_share, bool as_album, bool send_copy, bool remove_caption) TD_WARN_UNUSED_RESULT; + Result> resend_messages(DialogId dialog_id, vector message_ids) TD_WARN_UNUSED_RESULT; + Result send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl); Status send_screenshot_taken_notification_message(DialogId dialog_id); @@ -918,9 +920,9 @@ class MessagesManager : public Actor { mutable int32 last_access_date = 0; - uint64 send_message_logevent_id = 0; + mutable uint64 send_message_logevent_id = 0; - NetQueryRef send_query_ref; + mutable NetQueryRef send_query_ref; template void store(StorerT &storer) const; @@ -1355,6 +1357,8 @@ class MessagesManager : public Actor { Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward) const TD_WARN_UNUSED_RESULT; + static bool can_resend_message(const Message *m); + bool can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup = false) const; bool can_report_dialog(DialogId dialog_id) const; @@ -1400,12 +1404,13 @@ class MessagesManager : public Actor { tl_object_ptr input_encrypted_file, BufferSlice thumbnail); - void do_send_message(DialogId dialog_id, Message *m, vector bad_parts = {}); + void do_send_message(DialogId dialog_id, const Message *m, vector bad_parts = {}); - void on_message_media_uploaded(DialogId dialog_id, Message *m, tl_object_ptr &&input_media, - FileId file_id, FileId thumbnail_file_id); + void on_message_media_uploaded(DialogId dialog_id, const Message *m, + tl_object_ptr &&input_media, FileId file_id, + FileId thumbnail_file_id); - void on_secret_message_media_uploaded(DialogId dialog_id, Message *m, SecretInputMedia &&secret_input_media, + void on_secret_message_media_uploaded(DialogId dialog_id, const Message *m, SecretInputMedia &&secret_input_media, FileId file_id, FileId thumbnail_file_id); void on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id, Status result); @@ -1417,14 +1422,15 @@ class MessagesManager : public Actor { void on_yet_unsent_media_queue_updated(DialogId dialog_id); void save_send_bot_start_message_logevent(UserId bot_user_id, DialogId dialog_id, const string ¶meter, - Message *m); + const Message *m); - void do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter, Message *m); + void do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter, const Message *m); - void save_send_inline_query_result_message_logevent(DialogId dialog_id, Message *m, int64 query_id, + void save_send_inline_query_result_message_logevent(DialogId dialog_id, const Message *m, int64 query_id, const string &result_id); - void do_send_inline_query_result_message(DialogId dialog_id, Message *m, int64 query_id, const string &result_id); + void do_send_inline_query_result_message(DialogId dialog_id, const Message *m, int64 query_id, + const string &result_id); uint64 save_send_screenshot_taken_notification_message_logevent(DialogId dialog_id, const Message *m); @@ -2128,7 +2134,7 @@ class MessagesManager : public Actor { static void add_message_dependencies(Dependencies &dependencies, DialogId dialog_id, const Message *m); - void save_send_message_logevent(DialogId dialog_id, Message *m); + void save_send_message_logevent(DialogId dialog_id, const Message *m); uint64 save_change_dialog_report_spam_state_on_server_logevent(DialogId dialog_id, bool is_spam_dialog); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index edb8d768c..758ec4df1 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5831,6 +5831,18 @@ void Td::on_request(uint64 id, const td_api::forwardMessages &request) { messages_manager_->get_messages_object(-1, dialog_id, r_message_ids.ok())); } +void Td::on_request(uint64 id, const td_api::resendMessages &request) { + DialogId dialog_id(request.chat_id_); + auto r_message_ids = + messages_manager_->resend_messages(dialog_id, MessagesManager::get_message_ids(request.message_ids_)); + if (r_message_ids.is_error()) { + return send_closure(actor_id(this), &Td::send_error, id, r_message_ids.move_as_error()); + } + + send_closure(actor_id(this), &Td::send_result, id, + messages_manager_->get_messages_object(-1, dialog_id, r_message_ids.ok())); +} + void Td::on_request(uint64 id, td_api::getWebPagePreview &request) { CHECK_IS_USER(); CREATE_REQUEST(GetWebPagePreviewRequest, std::move(request.text_)); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index d6ccd4430..3f7a4258d 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -612,6 +612,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::forwardMessages &request); + void on_request(uint64 id, const td_api::resendMessages &request); + void on_request(uint64 id, td_api::getWebPagePreview &request); void on_request(uint64 id, td_api::getWebPageInstantView &request);