diff --git a/CMakeLists.txt b/CMakeLists.txt index 083ce390f..4bf53652a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.7.5 LANGUAGES CXX C) +project(TDLib VERSION 1.7.6 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") diff --git a/README.md b/README.md index f6d7b6a4c..d2ec01230 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic) Or you could install `TDLib` and then reference it in your CMakeLists.txt like this: ``` -find_package(Td 1.7.5 REQUIRED) +find_package(Td 1.7.6 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/tree/master/example/cpp/CMakeLists.txt). diff --git a/example/README.md b/example/README.md index 46a49addd..81de8f96d 100644 --- a/example/README.md +++ b/example/README.md @@ -43,10 +43,13 @@ TDLib can be used from Python through the [JSON](https://github.com/tdlib/td#usi Convenient Python wrappers already exist for our JSON interface. -If you use modern Python >= 3.6, take a look at [python-telegram](https://github.com/alexander-akhmetov/python-telegram). +If you use Python >= 3.6, take a look at [python-telegram](https://github.com/alexander-akhmetov/python-telegram). The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container. You can also try a fork [python-telegram](https://github.com/iTeam-co/python-telegram) of this library. +If you want to use TDLib with asyncio and Python >= 3.9, take a look at [aiotdlib](https://github.com/pylakey/aiotdlib). +This wrapper contains automatically generated fully-documented classes for all TDLib API types and functions and provides set of helper methods which makes work with TDLib much simpler. + For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib). This wrapper contains generator for TDLib API classes and basic interface for interaction with TDLib. diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index 337c8c332..bd2462000 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.7.5 REQUIRED) +find_package(Td 1.7.6 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index 0b726960a..028b129c2 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 0605df3b5..14037f289 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -466,7 +466,7 @@ chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = C //@can_edit_messages True, if the administrator can edit messages of other users and pin messages; applicable to channels only //@can_delete_messages True, if the administrator can delete messages of other users //@can_invite_users True, if the administrator can invite new users to the chat -//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members +//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members; always true for channels //@can_pin_messages True, if the administrator can pin messages; applicable to basic groups and supergroups only //@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them //@can_manage_voice_chats True, if the administrator can manage voice chats @@ -769,7 +769,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r //@reply_to_message_id If non-zero, the identifier of the message this message is replying to; can be the identifier of a deleted message //@message_thread_id If non-zero, the identifier of the message thread the message belongs to; unique within the chat to which the message belongs //@ttl For self-destructing messages, the message's TTL (Time To Live), in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the TTL expires -//@ttl_expires_in Time left before the message expires, in seconds +//@ttl_expires_in Time left before the message expires, in seconds. If the TTL timer isn't started yet, equals to the value of the ttl field //@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent //@author_signature For channel posts and anonymous group messages, optional author signature //@media_album_id Unique identifier of an album this message belongs to. Only audios, documents, photos and videos can be grouped together in albums @@ -1810,7 +1810,7 @@ textEntityTypeHashtag = TextEntityType; //@description A cashtag text, beginning with "$" and consisting of capital english letters (i.e. "$USD") textEntityTypeCashtag = TextEntityType; -//@description A bot command, beginning with "/". This shouldn't be highlighted if there are no bots in the chat +//@description A bot command, beginning with "/" textEntityTypeBotCommand = TextEntityType; //@description An HTTP URL @@ -3119,9 +3119,10 @@ messageLink link:string is_public:Bool = MessageLink; //@is_public True, if the link is a public link for a message in a chat //@chat_id If found, identifier of the chat to which the message belongs, 0 otherwise //@message If found, the linked message; may be null +//@media_timestamp Timestamp from which the video/audio/video note/voice note playing should start, in seconds; 0 if not specified. The media can be in the message content or in its link preview //@for_album True, if the whole media album to which the message belongs is linked //@for_comment True, if the message is linked as a channel post comment or from a message thread -messageLinkInfo is_public:Bool chat_id:int53 message:message for_album:Bool for_comment:Bool = MessageLinkInfo; +messageLinkInfo is_public:Bool chat_id:int53 message:message media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLinkInfo; //@description Contains a part of a file @data File bytes @@ -4181,9 +4182,10 @@ removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = //@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels. This is an offline request //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message +//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing should start, in seconds. The media can be in the message content or in its link preview //@for_album Pass true to create a link for the whole media album //@for_comment Pass true to create a link to the message as a channel post comment, or from a message thread -getMessageLink chat_id:int53 message_id:int53 for_album:Bool for_comment:Bool = MessageLink; +getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLink; //@description Returns an HTML code for embedding the message. Available only for messages in supergroups and channels with a username //@chat_id Identifier of the chat to which the message belongs @@ -4646,6 +4648,9 @@ getFileDownloadedPrefixSize file_id:int32 offset:int32 = Count; //@description Stops the downloading of a file. If a file has already been downloaded, does nothing @file_id Identifier of a file to stop downloading @only_if_pending Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server cancelDownloadFile file_id:int32 only_if_pending:Bool = Ok; +//@description Returns suggested name for saving a file in a given directory @file_id Identifier of the file @directory Directory in which the file is supposed to be saved +getSuggestedFileName file_id:int32 directory:string = Text; + //@description Asynchronously uploads a file to the cloud without sending it in a message. updateFile will be used to notify about upload progress and successful completion of the upload. The file will not have a persistent remote identifier until it will be sent in a message @file File to upload @file_type File type //@priority Priority of the upload (1-32). The higher the priority, the earlier the file will be uploaded. If the priorities of two files are equal, then the first one for which uploadFile was called will be uploaded first uploadFile file:InputFile file_type:FileType priority:int32 = File; diff --git a/td/mtproto/Handshake.cpp b/td/mtproto/Handshake.cpp index 1ec70aef2..23b32e758 100644 --- a/td/mtproto/Handshake.cpp +++ b/td/mtproto/Handshake.cpp @@ -47,12 +47,20 @@ static Result fetch_result(Slice message, bool check_end } AuthKeyHandshake::AuthKeyHandshake(int32 dc_id, int32 expires_in) - : mode_(expires_in == 0 ? Mode::Main : Mode::Temp), dc_id_(dc_id), expires_in_(expires_in) { + : mode_(expires_in == 0 ? Mode::Main : Mode::Temp) + , dc_id_(dc_id) + , expires_in_(expires_in) + , timeout_at_(Time::now() + 1e9) { +} + +void AuthKeyHandshake::set_timeout_in(double timeout_in) { + timeout_at_ = Time::now() + timeout_in; } void AuthKeyHandshake::clear() { last_query_ = BufferSlice(); state_ = Start; + timeout_at_ = Time::now() + 1e9; } bool AuthKeyHandshake::is_ready_for_finish() const { @@ -294,6 +302,10 @@ Status AuthKeyHandshake::on_start(Callback *connection) { Status AuthKeyHandshake::on_message(Slice message, Callback *connection, AuthKeyHandshakeContext *context) { Status status = [&] { + if (Time::now() >= timeout_at_) { + return Status::Error("Handshake timeout expired"); + } + switch (state_) { case ResPQ: return on_res_pq(message, connection, context->get_public_rsa_key_interface()); diff --git a/td/mtproto/Handshake.h b/td/mtproto/Handshake.h index 14077b546..435350d94 100644 --- a/td/mtproto/Handshake.h +++ b/td/mtproto/Handshake.h @@ -45,6 +45,8 @@ class AuthKeyHandshake { AuthKeyHandshake(int32 dc_id, int32 expires_in); + void set_timeout_in(double timeout_in); + bool is_ready_for_finish() const; void on_finish(); @@ -80,6 +82,8 @@ class AuthKeyHandshake { int32 expires_in_ = 0; double expires_at_ = 0; + double timeout_at_ = 0; + AuthKey auth_key_; double server_time_diff_ = 0; uint64 server_salt_ = 0; diff --git a/td/mtproto/HandshakeActor.cpp b/td/mtproto/HandshakeActor.cpp index d11a169c1..1f9c14b12 100644 --- a/td/mtproto/HandshakeActor.cpp +++ b/td/mtproto/HandshakeActor.cpp @@ -34,6 +34,7 @@ void HandshakeActor::close() { void HandshakeActor::start_up() { Scheduler::subscribe(connection_->get_poll_info().extract_pollable_fd(this)); set_timeout_in(timeout_); + handshake_->set_timeout_in(timeout_); yield(); } @@ -49,6 +50,26 @@ void HandshakeActor::loop() { } } +void HandshakeActor::hangup() { + finish(Status::Error(1, "Canceled")); + stop(); +} + +void HandshakeActor::timeout_expired() { + finish(Status::Error("Timeout expired")); + stop(); +} + +void HandshakeActor::tear_down() { + finish(Status::OK()); +} + +void HandshakeActor::finish(Status status) { + // NB: order may be important for parent + return_connection(std::move(status)); + return_handshake(); +} + void HandshakeActor::return_connection(Status status) { auto raw_connection = connection_->move_as_raw_connection(); if (!raw_connection) { diff --git a/td/mtproto/HandshakeActor.h b/td/mtproto/HandshakeActor.h index a49b56f89..61b5010e3 100644 --- a/td/mtproto/HandshakeActor.h +++ b/td/mtproto/HandshakeActor.h @@ -18,7 +18,7 @@ namespace td { namespace mtproto { -// Has Raw connection. Generates new auth key. And returns it and raw_connection. Or error... +// Owns RawConnection. Generates new auth key. And returns it and RawConnection. Or error... class HandshakeActor final : public Actor { public: HandshakeActor(unique_ptr handshake, unique_ptr raw_connection, @@ -36,26 +36,19 @@ class HandshakeActor final : public Actor { Promise> handshake_promise_; void start_up() final; - void tear_down() final { - finish(Status::OK()); - } - void hangup() final { - finish(Status::Error(1, "Canceled")); - stop(); - } - void timeout_expired() final { - finish(Status::Error("Timeout expired")); - stop(); - } + + void tear_down() final; + + void hangup() final; + + void timeout_expired() final; + void loop() final; - void finish(Status status) { - // NB: order may be important for parent - return_connection(std::move(status)); - return_handshake(); - } + void finish(Status status); void return_connection(Status status); + void return_handshake(); }; diff --git a/td/mtproto/PingConnection.cpp b/td/mtproto/PingConnection.cpp index 92945ea39..494b2d05d 100644 --- a/td/mtproto/PingConnection.cpp +++ b/td/mtproto/PingConnection.cpp @@ -146,7 +146,7 @@ class PingConnectionPingPong final LOG(ERROR) << "Unexpected message"; return Status::OK(); } - void on_message_result_error(uint64 id, int code, BufferSlice descr) final { + void on_message_result_error(uint64 id, int code, string message) final { } void on_message_failed(uint64 id, Status status) final { } diff --git a/td/mtproto/SessionConnection.cpp b/td/mtproto/SessionConnection.cpp index 7e9409b2f..47305b0af 100644 --- a/td/mtproto/SessionConnection.cpp +++ b/td/mtproto/SessionConnection.cpp @@ -302,10 +302,10 @@ Status SessionConnection::on_packet(const MsgInfo &info, uint64 req_msg_id, cons VLOG(mtproto) << "ERROR " << tag("code", rpc_error.error_code_) << tag("message", rpc_error.error_message_) << tag("req_msg_id", req_msg_id); if (req_msg_id != 0) { - callback_->on_message_result_error(req_msg_id, rpc_error.error_code_, as_buffer_slice(rpc_error.error_message_)); + callback_->on_message_result_error(req_msg_id, rpc_error.error_code_, rpc_error.error_message_.str()); } else { - LOG(WARNING) << "Receive rpc_error as update: [" << rpc_error.error_code_ << "][" << rpc_error.error_message_ - << "]"; + LOG(ERROR) << "Receive rpc_error as update: [" << rpc_error.error_code_ << "][" << rpc_error.error_message_ + << "]"; } return Status::OK(); } diff --git a/td/mtproto/SessionConnection.h b/td/mtproto/SessionConnection.h index bb43dd25f..24bc82103 100644 --- a/td/mtproto/SessionConnection.h +++ b/td/mtproto/SessionConnection.h @@ -111,7 +111,7 @@ class SessionConnection final virtual void on_message_ack(uint64 id) = 0; virtual Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) = 0; - virtual void on_message_result_error(uint64 id, int code, BufferSlice descr) = 0; + virtual void on_message_result_error(uint64 id, int code, string message) = 0; virtual void on_message_failed(uint64 id, Status status) = 0; virtual void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) = 0; diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index bfe84032f..cc7fd58ab 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -52,6 +52,7 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par auto my_id = ContactsManager::load_my_id(); if (my_id.is_valid()) { // just in case + LOG(INFO) << "Logged in as " << my_id; G()->shared_config().set_option_integer("my_id", my_id.get()); update_state(State::Ok); } else { @@ -917,6 +918,7 @@ void AuthManager::update_state(State new_state, bool force, bool should_save_sta bool AuthManager::load_state() { auto data = G()->td_db()->get_binlog_pmc()->get("auth_state"); if (data.empty()) { + LOG(INFO) << "Have no saved auth_state. Waiting for phone number"; return false; } DbState db_state; diff --git a/td/telegram/BotCommand.cpp b/td/telegram/BotCommand.cpp index 4cf4e8193..a5ffd3860 100644 --- a/td/telegram/BotCommand.cpp +++ b/td/telegram/BotCommand.cpp @@ -184,13 +184,11 @@ void set_commands(Td *td, td_api::object_ptr &&scope_pt Status::Error(400, PSLICE() << "Command length must not exceed " << MAX_COMMAND_TEXT_LENGTH)); } - const size_t MIN_COMMAND_DESCRIPTION_LENGTH = 3; const size_t MAX_COMMAND_DESCRIPTION_LENGTH = 256; command->description_ = trim(command->description_); auto description_length = utf8_length(command->description_); - if (description_length < MIN_COMMAND_DESCRIPTION_LENGTH) { - return promise.set_error(Status::Error( - 400, PSLICE() << "Command description length must be at least " << MIN_COMMAND_DESCRIPTION_LENGTH)); + if (command->description_.empty()) { + return promise.set_error(Status::Error(400, "Command description must be non-empty")); } if (description_length > MAX_COMMAND_DESCRIPTION_LENGTH) { return promise.set_error(Status::Error( diff --git a/td/telegram/CallActor.cpp b/td/telegram/CallActor.cpp index 7e3e892eb..6ef03cfd7 100644 --- a/td/telegram/CallActor.cpp +++ b/td/telegram/CallActor.cpp @@ -382,8 +382,8 @@ Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) { call_access_hash_ = call.access_hash_; is_call_id_inited_ = true; is_video_ |= (call.flags_ & telegram_api::phoneCallWaiting::VIDEO_MASK) != 0; - call_admin_id_ = call.admin_id_; - call_participant_id_ = call.participant_id_; + call_admin_user_id_ = UserId(call.admin_id_); + // call_participant_user_id_ = UserId(call.participant_id_); if (call_id_promise_) { call_id_promise_.set_value(std::move(call.id_)); } @@ -405,8 +405,8 @@ Status CallActor::do_update_call(telegram_api::phoneCallRequested &call) { call_access_hash_ = call.access_hash_; is_call_id_inited_ = true; is_video_ |= (call.flags_ & telegram_api::phoneCallRequested::VIDEO_MASK) != 0; - call_admin_id_ = call.admin_id_; - call_participant_id_ = call.participant_id_; + call_admin_user_id_ = UserId(call.admin_id_); + // call_participant_user_id_ = UserId(call.participant_id_); if (call_id_promise_) { call_id_promise_.set_value(std::move(call.id_)); } @@ -438,8 +438,8 @@ Status CallActor::do_update_call(telegram_api::phoneCallAccepted &call) { call_id_ = call.id_; call_access_hash_ = call.access_hash_; is_call_id_inited_ = true; - call_admin_id_ = call.admin_id_; - call_participant_id_ = call.participant_id_; + call_admin_user_id_ = UserId(call.admin_id_); + // call_participant_user_id_ = UserId(call.participant_id_); if (call_id_promise_) { call_id_promise_.set_value(std::move(call.id_)); } @@ -748,13 +748,13 @@ void CallActor::flush_call_state() { if (!has_notification_) { has_notification_ = true; send_closure(G()->notification_manager(), &NotificationManager::add_call_notification, - DialogId(UserId(call_admin_id_)), local_call_id_); + DialogId(call_admin_user_id_), local_call_id_); } } else { if (has_notification_) { has_notification_ = false; send_closure(G()->notification_manager(), &NotificationManager::remove_call_notification, - DialogId(UserId(call_admin_id_)), local_call_id_); + DialogId(call_admin_user_id_), local_call_id_); } } } @@ -767,9 +767,9 @@ void CallActor::flush_call_state() { // TODO can't call const function // send_closure(G()->contacts_manager(), &ContactsManager::get_user_id_object, user_id_, "flush_call_state"); send_closure(G()->td(), &Td::send_update, - make_tl_object( - make_tl_object(local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_id_, - is_outgoing_, is_video_, call_state_.get_call_state_object()))); + make_tl_object(make_tl_object( + local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_user_id_.get(), is_outgoing_, + is_video_, call_state_.get_call_state_object()))); } } diff --git a/td/telegram/CallActor.h b/td/telegram/CallActor.h index 8b88881e2..da5ee3a9c 100644 --- a/td/telegram/CallActor.h +++ b/td/telegram/CallActor.h @@ -142,8 +142,8 @@ class CallActor final : public NetQueryCallback { bool is_call_id_inited_{false}; bool has_notification_{false}; int64 call_access_hash_{0}; - int32 call_admin_id_{0}; - int32 call_participant_id_{0}; + UserId call_admin_user_id_; + // UserId call_participant_user_id_; CallState call_state_; bool call_state_need_flush_{false}; diff --git a/td/telegram/ClientJson.cpp b/td/telegram/ClientJson.cpp index 2f218d030..e67f62033 100644 --- a/td/telegram/ClientJson.cpp +++ b/td/telegram/ClientJson.cpp @@ -58,21 +58,18 @@ static string from_response(const td_api::Object &object, const string &extra, i auto buf = StackAllocator::alloc(1 << 18); JsonBuilder jb(StringBuilder(buf.as_slice(), true), -1); jb.enter_value() << ToJson(object); - auto slice = jb.string_builder().as_cslice(); + auto &sb = jb.string_builder(); + auto slice = sb.as_cslice(); CHECK(!slice.empty() && slice.back() == '}'); - string str; - str.reserve(slice.size() + (extra.empty() ? 0 : 10 + extra.size()) + (client_id == 0 ? 0 : 14 + 10)); - str.append(slice.begin(), slice.size() - 1); + sb.pop_back(); if (!extra.empty()) { - str += ",\"@extra\":"; - str += extra; + sb << ",\"@extra\":" << extra; } if (client_id != 0) { - str += ",\"@client_id\":"; - str += to_string(client_id); + sb << ",\"@client_id\":" << client_id; } - str += '}'; - return str; + sb << '}'; + return sb.as_cslice().str(); } static TD_THREAD_LOCAL string *current_output; diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 54c4b1d33..361838ff3 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -4982,6 +4982,7 @@ void ContactsManager::set_my_id(UserId my_id) { my_id_ = my_id; G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); G()->shared_config().set_option_integer("my_id", my_id_.get()); + G()->td_db()->get_binlog_pmc()->force_sync(Promise()); } } @@ -11887,6 +11888,9 @@ void ContactsManager::speculative_add_channel_participants(ChannelId channel_id, channel_full->bot_user_ids.push_back(user_id); channel_full->need_save_to_database = true; reload_channel_full(channel_id, Promise(), "speculative_add_channel_participants"); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); } } if (is_participants_cache_changed) { @@ -11924,6 +11928,9 @@ void ContactsManager::speculative_delete_channel_participant(ChannelId channel_i if (channel_full != nullptr && td::remove(channel_full->bot_user_ids, deleted_user_id)) { channel_full->need_save_to_database = true; update_channel_full(channel_full, channel_id); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); } } @@ -12061,10 +12068,16 @@ void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId channel_full->bot_user_ids.push_back(user_id); channel_full->need_save_to_database = true; reload_channel_full(channel_id, Promise(), "speculative_add_channel_user"); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); } } else { if (td::remove(channel_full->bot_user_ids, user_id)) { channel_full->need_save_to_database = true; + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); } } } @@ -14907,6 +14920,7 @@ void ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, Promi send_closure(actor_id, &ContactsManager::finish_get_chat_participant, chat_id, user_id, std::move(promise)); }); send_get_chat_full_query(chat_id, std::move(query_promise), "get_chat_participant"); + return; } if (is_chat_full_outdated(chat_full, c, chat_id)) { diff --git a/td/telegram/Game.cpp b/td/telegram/Game.cpp index 9d68e8685..6704dbc10 100644 --- a/td/telegram/Game.cpp +++ b/td/telegram/Game.cpp @@ -91,9 +91,9 @@ const FormattedText &Game::get_text() const { return text_; } -tl_object_ptr Game::get_game_object(Td *td) const { +tl_object_ptr Game::get_game_object(Td *td, bool skip_bot_commands) const { return make_tl_object( - id_, short_name_, title_, get_formatted_text_object(text_), description_, + id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands), description_, get_photo_object(td->file_manager_.get(), photo_), td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object")); } diff --git a/td/telegram/Game.h b/td/telegram/Game.h index 2d4197f00..1333e268e 100644 --- a/td/telegram/Game.h +++ b/td/telegram/Game.h @@ -63,7 +63,7 @@ class Game { const FormattedText &get_text() const; - tl_object_ptr get_game_object(Td *td) const; + tl_object_ptr get_game_object(Td *td, bool skip_bot_commands) const; bool has_input_media() const; diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index 612434039..e5656ea05 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -1282,7 +1282,7 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI std::move(result->document_), DialogId()); game->id_ = std::move(result->id_); - game->game_ = inline_game.get_game_object(td_); + game->game_ = inline_game.get_game_object(td_, true); if (!register_inline_message_content(results->query_id_, game->id_, FileId(), std::move(result->send_message_), td_api::inputMessageGame::ID, diff --git a/td/telegram/InputMessageText.cpp b/td/telegram/InputMessageText.cpp index 8d53a367f..352a49dd4 100644 --- a/td/telegram/InputMessageText.cpp +++ b/td/telegram/InputMessageText.cpp @@ -39,7 +39,7 @@ Result process_input_message_text(const ContactsManager *conta } TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(input_message_text->text_->entities_))); - auto need_skip_commands = need_skip_bot_commands(contacts_manager, dialog_id, is_bot); + auto need_skip_commands = need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot); bool parse_markdown = G()->shared_config().get_option_boolean("always_parse_markdown"); TRY_STATUS(fix_formatted_text(input_message_text->text_->text_, entities, for_draft, parse_markdown, need_skip_commands, for_draft)); @@ -54,7 +54,7 @@ Result process_input_message_text(const ContactsManager *conta } td_api::object_ptr get_input_message_text_object(const InputMessageText &input_message_text) { - return td_api::make_object(get_formatted_text_object(input_message_text.text), + return td_api::make_object(get_formatted_text_object(input_message_text.text, false), input_message_text.disable_web_page_preview, input_message_text.clear_draft); } diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index 5d05e9880..e68063f3d 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -191,7 +191,8 @@ class LinkManager::InternalLinkMessageDraft final : public InternalLink { bool contains_link_ = false; td_api::object_ptr get_internal_link_type_object() const final { - return td_api::make_object(get_formatted_text_object(text_), contains_link_); + return td_api::make_object(get_formatted_text_object(text_, true), + contains_link_); } public: @@ -1124,6 +1125,7 @@ Result LinkManager::get_message_link_info(Slice url) { Slice channel_id_slice; Slice message_id_slice; Slice comment_message_id_slice = "0"; + Slice media_timestamp_slice; bool is_single = false; bool for_comment = false; if (link_info.is_tg_) { @@ -1166,6 +1168,9 @@ Result LinkManager::get_message_link_info(Slice url) { message_id_slice = key_value.second; } } + if (key_value.first == "t") { + media_timestamp_slice = key_value.second; + } if (key_value.first == "single") { is_single = true; } @@ -1205,6 +1210,9 @@ Result LinkManager::get_message_link_info(Slice url) { auto args = full_split(url.substr(query_pos + 1), '&'); for (auto arg : args) { auto key_value = split(arg, '='); + if (key_value.first == "t") { + media_timestamp_slice = key_value.second; + } if (key_value.first == "single") { is_single = true; } @@ -1238,11 +1246,49 @@ Result LinkManager::get_message_link_info(Slice url) { return Status::Error("Wrong comment message ID"); } + bool is_media_timestamp_invalid = false; + int32 media_timestamp = 0; + const int32 MAX_MEDIA_TIMESTAMP = 10000000; + if (!media_timestamp_slice.empty()) { + int32 current_value = 0; + for (size_t i = 0; i <= media_timestamp_slice.size(); i++) { + auto c = i < media_timestamp_slice.size() ? media_timestamp_slice[i] : 's'; + if ('0' <= c && c <= '9') { + current_value = current_value * 10 + c - '0'; + if (current_value > MAX_MEDIA_TIMESTAMP) { + is_media_timestamp_invalid = true; + break; + } + } else { + auto mul = 0; + switch (to_lower(c)) { + case 'h': + mul = 3600; + break; + case 'm': + mul = 60; + break; + case 's': + mul = 1; + break; + } + if (mul == 0 || current_value > MAX_MEDIA_TIMESTAMP / mul || + media_timestamp + current_value * mul > MAX_MEDIA_TIMESTAMP) { + is_media_timestamp_invalid = true; + break; + } + media_timestamp += current_value * mul; + current_value = 0; + } + } + } + MessageLinkInfo info; info.username = username.str(); info.channel_id = channel_id; info.message_id = MessageId(ServerMessageId(r_message_id.ok())); info.comment_message_id = MessageId(ServerMessageId(r_comment_message_id.ok())); + info.media_timestamp = is_media_timestamp_invalid ? 0 : media_timestamp; info.is_single = is_single; info.for_comment = for_comment; LOG(INFO) << "Have link to " << info.message_id << " in chat @" << info.username << "/" << channel_id.get(); diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index cf44ee341..e6c15a3c2 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -2810,6 +2810,19 @@ void remove_message_content_web_page(MessageContent *content) { static_cast(content)->web_page_id = WebPageId(); } +bool can_message_content_have_media_timestamp(const MessageContent *content) { + CHECK(content != nullptr); + switch (content->get_type()) { + case MessageContentType::Audio: + case MessageContentType::Video: + case MessageContentType::VideoNote: + case MessageContentType::VoiceNote: + return true; + default: + return has_message_content_web_page(content); + } +} + void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id, vector &&option_ids, Promise &&promise) { CHECK(content->get_type() == MessageContentType::Poll); @@ -2901,9 +2914,9 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo if (old_->text.text != new_->text.text) { if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) { LOG(ERROR) << "Message text has changed from " - << to_string(get_message_content_object(old_content, td, dialog_id, -1, false)) + << to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false)) << ". New content is " - << to_string(get_message_content_object(new_content, td, dialog_id, -1, false)); + << to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false)); } need_update = true; } @@ -2913,9 +2926,9 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT && need_message_entities_changed_warning(old_->text.entities, new_->text.entities)) { LOG(WARNING) << "Entities has changed from " - << to_string(get_message_content_object(old_content, td, dialog_id, -1, false)) + << to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false)) << ". New content is " - << to_string(get_message_content_object(new_content, td, dialog_id, -1, false)); + << to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false)); } need_update = true; } @@ -4634,19 +4647,19 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr get_message_content_object(const MessageContent *content, Td *td, DialogId dialog_id, int32 message_date, - bool is_content_secret) { + bool is_content_secret, bool skip_bot_commands) { CHECK(content != nullptr); switch (content->get_type()) { case MessageContentType::Animation: { const MessageAnimation *m = static_cast(content); return make_tl_object( td->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"), - get_formatted_text_object(m->caption), is_content_secret); + get_formatted_text_object(m->caption, skip_bot_commands), is_content_secret); } case MessageContentType::Audio: { const MessageAudio *m = static_cast(content); return make_tl_object(td->audios_manager_->get_audio_object(m->file_id), - get_formatted_text_object(m->caption)); + get_formatted_text_object(m->caption, skip_bot_commands)); } case MessageContentType::Contact: { const MessageContact *m = static_cast(content); @@ -4656,11 +4669,11 @@ tl_object_ptr get_message_content_object(const MessageCo const MessageDocument *m = static_cast(content); return make_tl_object( td->documents_manager_->get_document_object(m->file_id, PhotoFormat::Jpeg), - get_formatted_text_object(m->caption)); + get_formatted_text_object(m->caption, skip_bot_commands)); } case MessageContentType::Game: { const MessageGame *m = static_cast(content); - return make_tl_object(m->game.get_game_object(td)); + return make_tl_object(m->game.get_game_object(td, skip_bot_commands)); } case MessageContentType::Invoice: { const MessageInvoice *m = static_cast(content); @@ -4682,7 +4695,8 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::Photo: { const MessagePhoto *m = static_cast(content); return make_tl_object(get_photo_object(td->file_manager_.get(), m->photo), - get_formatted_text_object(m->caption), is_content_secret); + get_formatted_text_object(m->caption, skip_bot_commands), + is_content_secret); } case MessageContentType::Sticker: { const MessageSticker *m = static_cast(content); @@ -4690,7 +4704,7 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::Text: { const MessageText *m = static_cast(content); - return make_tl_object(get_formatted_text_object(m->text), + return make_tl_object(get_formatted_text_object(m->text, skip_bot_commands), td->web_pages_manager_->get_web_page_object(m->web_page_id)); } case MessageContentType::Unsupported: @@ -4702,7 +4716,8 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::Video: { const MessageVideo *m = static_cast(content); return make_tl_object(td->videos_manager_->get_video_object(m->file_id), - get_formatted_text_object(m->caption), is_content_secret); + get_formatted_text_object(m->caption, skip_bot_commands), + is_content_secret); } case MessageContentType::VideoNote: { const MessageVideoNote *m = static_cast(content); @@ -4712,7 +4727,8 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::VoiceNote: { const MessageVoiceNote *m = static_cast(content); return make_tl_object(td->voice_notes_manager_->get_voice_note_object(m->file_id), - get_formatted_text_object(m->caption), m->is_listened); + get_formatted_text_object(m->caption, skip_bot_commands), + m->is_listened); } case MessageContentType::ChatCreate: { const MessageChatCreate *m = static_cast(content); @@ -4901,6 +4917,10 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td) auto audio_file_id = static_cast(content)->file_id; return td->audios_manager_->get_audio_duration(audio_file_id); } + case MessageContentType::Text: { + auto web_page_id = static_cast(content)->web_page_id; + return td->web_pages_manager_->get_web_page_duration(web_page_id); + } case MessageContentType::Video: { auto video_file_id = static_cast(content)->file_id; return td->videos_manager_->get_video_duration(video_file_id); diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index 891ebfeef..fc564cab3 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -149,6 +149,8 @@ bool has_message_content_web_page(const MessageContent *content); void remove_message_content_web_page(MessageContent *content); +bool can_message_content_have_media_timestamp(const MessageContent *content); + void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id, vector &&option_ids, Promise &&promise); @@ -195,7 +197,7 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr get_message_content_object(const MessageContent *content, Td *td, DialogId dialog_id, int32 message_date, - bool is_content_secret); + bool is_content_secret, bool skip_bot_commands); const FormattedText *get_message_content_text(const MessageContent *content); diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index af401f6fb..c284b8ad7 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -140,11 +140,15 @@ tl_object_ptr MessageEntity::get_text_entity_object() const return make_tl_object(offset, length, get_text_entity_type_object()); } -vector> get_text_entities_object(const vector &entities) { +vector> get_text_entities_object(const vector &entities, + bool skip_bot_commands) { vector> result; result.reserve(entities.size()); for (auto &entity : entities) { + if (skip_bot_commands && entity.type == MessageEntity::Type::BotCommand) { + continue; + } auto entity_object = entity.get_text_entity_object(); if (entity_object->type_ != nullptr) { result.push_back(std::move(entity_object)); @@ -158,8 +162,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const FormattedText &te return string_builder << '"' << text.text << "\" with entities " << text.entities; } -td_api::object_ptr get_formatted_text_object(const FormattedText &text) { - return td_api::make_object(text.text, get_text_entities_object(text.entities)); +td_api::object_ptr get_formatted_text_object(const FormattedText &text, bool skip_bot_commands) { + return td_api::make_object(text.text, + get_text_entities_object(text.entities, skip_bot_commands)); } static bool is_word_character(uint32 code) { @@ -3877,6 +3882,7 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m auto debug_entities = entities; auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, false); if (status.is_error()) { + // message entities in media albums can be wrong because of a long time ago fixed server-side bug if (!from_album && (send_date == 0 || send_date > 1600340000)) { // approximate fix date LOG(ERROR) << "Receive error " << status << " while parsing message text from " << source << " sent at " << send_date << " with content \"" << debug_message_text << "\" -> \"" << message_text @@ -3929,7 +3935,7 @@ Result process_input_caption(const ContactsManager *contacts_mana } TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(caption->entities_))); TRY_STATUS(fix_formatted_text(caption->text_, entities, true, false, - need_skip_bot_commands(contacts_manager, dialog_id, is_bot), false)); + need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot), false)); return FormattedText{std::move(caption->text_), std::move(entities)}; } @@ -3944,7 +3950,19 @@ void add_formatted_text_dependencies(Dependencies &dependencies, const Formatted } } -bool need_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot) { +bool has_bot_commands(const FormattedText *text) { + if (text == nullptr) { + return false; + } + for (auto &entity : text->entities) { + if (entity.type == MessageEntity::Type::BotCommand) { + return true; + } + } + return false; +} + +bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot) { if (!dialog_id.is_valid()) { return true; } diff --git a/td/telegram/MessageEntity.h b/td/telegram/MessageEntity.h index f731b29e5..aa8b70bdc 100644 --- a/td/telegram/MessageEntity.h +++ b/td/telegram/MessageEntity.h @@ -131,9 +131,10 @@ Result> get_message_entities(const ContactsManager *contac vector> &&input_entities, bool allow_all = false); -vector> get_text_entities_object(const vector &entities); +vector> get_text_entities_object(const vector &entities, + bool skip_bot_commands); -td_api::object_ptr get_formatted_text_object(const FormattedText &text); +td_api::object_ptr get_formatted_text_object(const FormattedText &text, bool skip_bot_commands); vector find_entities(Slice text, bool skip_bot_commands); @@ -191,6 +192,8 @@ Result process_input_caption(const ContactsManager *contacts_mana void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text); -bool need_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot); +bool has_bot_commands(const FormattedText *text); + +bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot); } // namespace td diff --git a/td/telegram/MessageLinkInfo.h b/td/telegram/MessageLinkInfo.h index a580ebe1b..65c70c46e 100644 --- a/td/telegram/MessageLinkInfo.h +++ b/td/telegram/MessageLinkInfo.h @@ -21,6 +21,7 @@ struct MessageLinkInfo { MessageId message_id; bool is_single = false; + int32 media_timestamp = 0; DialogId comment_dialog_id; MessageId comment_message_id; diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 3a036effe..8959b58a3 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -5318,6 +5318,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { last_database_message = get_message(this, last_database_message_id); } + auto dialog_type = dialog_id.get_type(); bool has_draft_message = draft_message != nullptr; bool has_last_database_message = last_database_message != nullptr; bool has_first_database_message_id = first_database_message_id.is_valid(); @@ -5347,6 +5348,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { bool has_active_group_call_id = active_group_call_id.is_valid(); bool has_message_ttl_setting = !message_ttl_setting.is_empty(); bool has_default_join_group_call_as_dialog_id = default_join_group_call_as_dialog_id.is_valid(); + bool store_has_bots = dialog_type == DialogType::Chat || dialog_type == DialogType::Channel; BEGIN_STORE_FLAGS(); STORE_FLAG(has_draft_message); STORE_FLAG(has_last_database_message); @@ -5409,6 +5411,8 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(has_message_ttl_setting); STORE_FLAG(is_message_ttl_setting_inited); STORE_FLAG(has_default_join_group_call_as_dialog_id); + STORE_FLAG(store_has_bots ? has_bots : false); + STORE_FLAG(store_has_bots ? is_has_bots_inited : false); // 26 END_STORE_FLAGS(); } @@ -5596,6 +5600,8 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(has_message_ttl_setting); PARSE_FLAG(is_message_ttl_setting_inited); PARSE_FLAG(has_default_join_group_call_as_dialog_id); + PARSE_FLAG(has_bots); + PARSE_FLAG(is_has_bots_inited); END_PARSE_FLAGS(); } else { is_folder_id_inited = false; @@ -5616,6 +5622,8 @@ void MessagesManager::Dialog::parse(ParserT &parser) { is_group_call_empty = false; can_invite_members = false; is_message_ttl_setting_inited = false; + has_bots = false; + is_has_bots_inited = false; } parse(last_new_message_id, parser); @@ -6674,10 +6682,10 @@ void MessagesManager::on_update_service_notification(tl_object_ptrget_type()); if ((update->flags_ & telegram_api::updateServiceNotification::POPUP_MASK) != 0) { - send_closure( - G()->td(), &Td::send_update, - td_api::make_object( - update->type_, get_message_content_object(content.get(), td_, owner_dialog_id, date, is_content_secret))); + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + update->type_, + get_message_content_object(content.get(), td_, owner_dialog_id, date, is_content_secret, true))); } if (has_date && is_user) { Dialog *d = get_service_notifications_dialog(); @@ -7092,6 +7100,20 @@ void MessagesManager::on_update_some_live_location_viewed(Promise &&promis promise.set_value(Unit()); } +bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, const Message *m) const { + if (td_->auth_manager_->is_bot()) { + return false; + } + + if (m != nullptr && m->message_id.is_scheduled()) { + return true; + } + + auto d = get_dialog(dialog_id); + CHECK(d != nullptr); + return (d->is_has_bots_inited && !d->has_bots) || is_broadcast_channel(dialog_id); +} + void MessagesManager::on_external_update_message_content(FullMessageId full_message_id) { const Dialog *d = get_dialog(full_message_id.get_dialog_id()); if (d == nullptr) { @@ -7100,9 +7122,7 @@ void MessagesManager::on_external_update_message_content(FullMessageId full_mess } const Message *m = get_message(d, full_message_id.get_message_id()); CHECK(m != nullptr); - auto live_location_date = m->is_failed_to_send ? 0 : m->date; - send_update_message_content(full_message_id.get_dialog_id(), m->message_id, m->content.get(), live_location_date, - m->is_content_secret, "on_external_update_message_content"); + send_update_message_content(d->dialog_id, m, "on_external_update_message_content"); if (m->message_id == d->last_message_id) { send_update_chat_last_message_impl(d, "on_external_update_message_content"); } @@ -7517,7 +7537,8 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p if (old_pts != new_pts - pts_count) { LOG(INFO) << "Found a gap in the " << dialog_id << " with pts = " << old_pts << ". new_pts = " << new_pts << ", pts_count = " << pts_count << " in update from " << source; - if (d->order != DEFAULT_ORDER || is_dialog_sponsored(d) || d->was_opened) { + if (d->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() || + is_dialog_sponsored(d)) { d->postponed_channel_updates.emplace( new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise))); @@ -10737,10 +10758,12 @@ void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from } bool allow_error = d->messages == nullptr; + auto old_order = d->order; delete_all_dialog_messages(d, remove_from_dialog_list, true); - if (last_new_message_id.is_valid() && last_new_message_id == d->max_unavailable_message_id && !revoke) { + if (last_new_message_id.is_valid() && last_new_message_id == d->max_unavailable_message_id && !revoke && + !(old_order != DEFAULT_ORDER && remove_from_dialog_list)) { // history has already been cleared, nothing to do promise.set_value(Unit()); return; @@ -11061,12 +11084,14 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { } if (!d->has_unload_timeout) { + LOG(INFO) << "Don't need to unload " << dialog_id; // possible right after the dialog was opened return; } if (!is_message_unload_enabled()) { // just in case + LOG(INFO) << "Message unload is disabled in " << dialog_id; d->has_unload_timeout = false; return; } @@ -12327,8 +12352,7 @@ void MessagesManager::on_message_ttl_expired(Dialog *d, Message *m) { remove_message_file_sources(d->dialog_id, m); on_message_ttl_expired_impl(d, m); register_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired"); - send_update_message_content(d->dialog_id, m->message_id, m->content.get(), m->date, m->is_content_secret, - "on_message_ttl_expired"); + send_update_message_content(d->dialog_id, m, "on_message_ttl_expired"); } void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *m) { @@ -13721,7 +13745,7 @@ std::pair> MessagesManager::creat LOG(ERROR) << "Receive media group identifier " << message_info.media_album_id << " in " << message_id << " from " << dialog_id << " with content " << oneline(to_string(get_message_content_object(message->content.get(), td_, dialog_id, message->date, - is_content_secret))); + is_content_secret, false))); } else { message->media_album_id = message_info.media_album_id; } @@ -13849,6 +13873,10 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f need_update = false; + if (old_message_id.is_valid() && message_id.is_valid() && message_id < old_message_id) { + LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << message_id; + } + set_message_id(new_message, old_message_id); new_message->from_database = false; new_message->have_previous = false; @@ -14406,12 +14434,12 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, } auto full_message_id = it->second; - auto dialog_id = full_message_id.get_dialog_id(); auto m = get_message_force(full_message_id, "on_update_sent_text_message"); if (m == nullptr) { // message has already been deleted return; } + auto dialog_id = full_message_id.get_dialog_id(); full_message_id = FullMessageId(dialog_id, m->message_id); if (m->content->get_type() != MessageContentType::Text) { @@ -14443,8 +14471,7 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, m->is_content_secret = is_secret_message_content(m->ttl, MessageContentType::Text); } if (need_update) { - send_update_message_content(dialog_id, m->message_id, m->content.get(), m->date, m->is_content_secret, - "on_update_sent_text_message"); + send_update_message_content(dialog_id, m, "on_update_sent_text_message"); } } @@ -14661,6 +14688,11 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectoris_has_bots_inited && !td_->auth_manager_->is_bot()) { + // asynchronously get has_bots from the server + // TODO add has_bots to telegram_api::dialog + get_dialog_info_full(dialog_id, Auto()); + } if (!d->is_last_pinned_message_id_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog pinned message from the server get_dialog_pinned_message(dialog_id, Auto()); @@ -15241,6 +15273,8 @@ unique_ptr MessagesManager::do_delete_message(Dialog * m->have_previous, m->have_next, source); } + delete_bot_command_message_id(d->dialog_id, message_id); + bool need_get_history = false; if (!only_from_memory) { LOG(INFO) << "Deleting " << full_message_id << " with have_previous = " << m->have_previous @@ -17528,8 +17562,8 @@ bool MessagesManager::is_message_edited_recently(FullMessageId full_message_id, return m->edit_date >= G()->unix_time() - seconds; } -Result> MessagesManager::get_message_link(FullMessageId full_message_id, bool for_group, - bool for_comment) { +Result> MessagesManager::get_message_link(FullMessageId full_message_id, int32 media_timestamp, + bool for_group, bool for_comment) { auto dialog_id = full_message_id.get_dialog_id(); auto d = get_dialog_force(dialog_id, "get_message_link"); if (d == nullptr) { @@ -17570,10 +17604,22 @@ Result> MessagesManager::get_message_link(FullMessageId for_comment = false; } + if (media_timestamp <= 0 || !can_message_content_have_media_timestamp(m->content.get())) { + media_timestamp = 0; + } + if (media_timestamp != 0) { + auto duration = get_message_content_duration(m->content.get(), td_); + if (duration != 0 && media_timestamp > duration) { + media_timestamp = 0; + } + } + td_->create_handler(Promise()) ->send(dialog_id.get_channel_id(), m->message_id, for_group, true); - auto t_me = G()->shared_config().get_option_string("t_me_url", "https://t.me/"); + SliceBuilder sb; + sb << G()->shared_config().get_option_string("t_me_url", "https://t.me/"); + if (for_comment) { auto *top_m = get_message_force(d, m->top_thread_message_id, "get_public_message_link"); if (is_discussion_message(dialog_id, top_m) && is_active_message_reply_info(dialog_id, top_m->reply_info)) { @@ -17587,38 +17633,49 @@ Result> MessagesManager::get_message_link(FullMessageId if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info) && linked_message_id.is_server() && have_input_peer(linked_dialog_id, AccessRights::Read) && !channel_username.empty()) { - return std::make_pair( - PSTRING() << t_me << channel_username << '/' << linked_message_id.get_server_message_id().get() - << "?comment=" << m->message_id.get_server_message_id().get() << (for_group ? "" : "&single"), - true); + sb << channel_username << '/' << linked_message_id.get_server_message_id().get() + << "?comment=" << m->message_id.get_server_message_id().get(); + if (!for_group) { + sb << "&single"; + } + if (media_timestamp > 0) { + sb << "&t=" << media_timestamp; + } + return std::make_pair(sb.as_cslice().str(), true); } } } auto dialog_username = td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()); - if (m->content->get_type() == MessageContentType::VideoNote && is_broadcast_channel(dialog_id) && - !dialog_username.empty()) { + bool is_public = !dialog_username.empty(); + if (m->content->get_type() == MessageContentType::VideoNote && is_broadcast_channel(dialog_id) && is_public) { return std::make_pair( PSTRING() << "https://telesco.pe/" << dialog_username << '/' << m->message_id.get_server_message_id().get(), true); } - string args; + if (is_public) { + sb << dialog_username; + } else { + sb << "c/" << dialog_id.get_channel_id().get(); + } + sb << '/' << m->message_id.get_server_message_id().get(); + + char separator = '?'; if (for_comment) { - args = PSTRING() << "?thread=" << m->top_thread_message_id.get_server_message_id().get(); + sb << separator << "thread=" << m->top_thread_message_id.get_server_message_id().get(); + separator = '&'; } if (!for_group) { - args += args.empty() ? '?' : '&'; - args += "single"; + sb << separator << "single"; + separator = '&'; + } + if (media_timestamp > 0) { + sb << separator << "t=" << media_timestamp; + separator = '&'; } - bool is_public = !dialog_username.empty(); - if (!is_public) { - dialog_username = PSTRING() << "c/" << dialog_id.get_channel_id().get(); - } - return std::make_pair(PSTRING() << G()->shared_config().get_option_string("t_me_url", "https://t.me/") - << dialog_username << '/' << m->message_id.get_server_message_id().get() << args, - is_public); + return std::make_pair(sb.as_cslice().str(), is_public); } string MessagesManager::get_message_embedding_code(FullMessageId full_message_id, bool for_group, @@ -17797,6 +17854,7 @@ td_api::object_ptr MessagesManager::get_message_link_in : (is_public ? resolve_dialog_username(info.username) : DialogId(info.channel_id)); MessageId message_id = info.comment_dialog_id.is_valid() ? info.comment_message_id : info.message_id; td_api::object_ptr message; + int32 media_timestamp = 0; bool for_album = false; bool for_comment = false; @@ -17809,11 +17867,17 @@ td_api::object_ptr MessagesManager::get_message_link_in message = get_message_object(dialog_id, m); for_album = !info.is_single && m->media_album_id != 0; for_comment = (info.comment_dialog_id.is_valid() || info.for_comment) && m->top_thread_message_id.is_valid(); + if (can_message_content_have_media_timestamp(m->content.get())) { + auto duration = get_message_content_duration(m->content.get(), td_); + if (duration == 0 || info.media_timestamp <= duration) { + media_timestamp = info.media_timestamp; + } + } } } - return td_api::make_object(is_public, dialog_id.get(), std::move(message), for_album, - for_comment); + return td_api::make_object(is_public, dialog_id.get(), std::move(message), media_timestamp, + for_album, for_comment); } InputDialogId MessagesManager::get_input_dialog_id(DialogId dialog_id) const { @@ -21590,6 +21654,30 @@ void MessagesManager::on_message_live_location_viewed_on_server(int64 task_id) { pending_message_live_location_view_timeout_.add_timeout_in(task_id, LIVE_LOCATION_VIEW_PERIOD); } +void MessagesManager::try_add_bot_command_message_id(DialogId dialog_id, const Message *m) { + CHECK(m != nullptr); + if (td_->auth_manager_->is_bot() || !is_group_dialog(dialog_id) || m->message_id.is_scheduled() || + !has_bot_commands(get_message_content_text(m->content.get()))) { + return; + } + + dialog_bot_command_message_ids_[dialog_id].message_ids.insert(m->message_id); +} + +void MessagesManager::delete_bot_command_message_id(DialogId dialog_id, MessageId message_id) { + if (message_id.is_scheduled()) { + return; + } + auto it = dialog_bot_command_message_ids_.find(dialog_id); + if (it == dialog_bot_command_message_ids_.end()) { + return; + } + it->second.message_ids.erase(message_id); + if (it->second.message_ids.empty()) { + dialog_bot_command_message_ids_.erase(it); + } +} + FileSourceId MessagesManager::get_message_file_source_id(FullMessageId full_message_id) { if (td_->auth_manager_->is_bot()) { return FileSourceId(); @@ -22530,6 +22618,9 @@ void MessagesManager::get_history_from_the_end_impl(const Dialog *d, bool from_d if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); } + if (!d->first_database_message_id.is_valid() && !d->have_full_history) { + from_database = false; + } int32 limit = MAX_GET_HISTORY; if (from_database && G()->parameters().use_message_db) { if (!promise) { @@ -22579,6 +22670,10 @@ void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_i if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); } + if ((!d->first_database_message_id.is_valid() || from_message_id <= d->first_database_message_id) && + !d->have_full_history) { + from_database = false; + } if (from_database && G()->parameters().use_message_db) { LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << " from database"; @@ -22627,13 +22722,15 @@ void MessagesManager::load_messages_impl(const Dialog *d, MessageId from_message only_local = true; } bool from_database = (left_tries > 2 || only_local) && G()->parameters().use_message_db; - // TODO do not send requests to database if (from_message_id < d->first_database_message_id || - // !d->first_database_message_id.is_valid()) && !d->have_full_history if (from_message_id == MessageId()) { get_history_from_the_end_impl(d, from_database, only_local, std::move(promise)); return; } + if ((!d->first_database_message_id.is_valid() || from_message_id <= d->first_database_message_id) && + !d->have_full_history) { + from_database = false; + } if (offset >= -1) { // get history before some server or local message limit = min(max(limit + offset + 1, MAX_GET_HISTORY / 2), MAX_GET_HISTORY); @@ -22993,13 +23090,13 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial double ttl_expires_in = 0; if (!for_event_log) { if (m->ttl_expires_at != 0) { - ttl_expires_in = max(m->ttl_expires_at - Time::now(), 1e-3); + ttl_expires_in = clamp(m->ttl_expires_at - Time::now(), 1e-3, ttl - 1e-3); } else { - ttl_expires_in = m->ttl; + ttl_expires_in = ttl; } if (ttl == 0 && m->ttl_period != 0) { ttl = m->ttl_period; - ttl_expires_in = max(m->date + m->ttl_period - G()->server_time(), 1e-3); + ttl_expires_in = clamp(m->date + m->ttl_period - G()->server_time(), 1e-3, ttl - 1e-3); } } else { ttl = 0; @@ -23020,6 +23117,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto date = is_scheduled ? 0 : m->date; auto edit_date = m->hide_edit_date ? 0 : m->edit_date; auto is_pinned = for_event_log || is_scheduled ? false : m->is_pinned; + bool skip_bot_commands = for_event_log || need_skip_bot_commands(dialog_id, m); return make_tl_object( m->message_id.get(), get_message_sender_object_const(m->sender_user_id, m->sender_dialog_id), dialog_id.get(), std::move(sending_state), std::move(scheduling_state), is_outgoing, is_pinned, can_be_edited, can_be_forwarded, @@ -23028,7 +23126,8 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial get_message_interaction_info_object(dialog_id, m), reply_in_dialog_id.get(), reply_to_message_id, top_thread_message_id, ttl, ttl_expires_in, via_bot_user_id, m->author_signature, media_album_id, get_restriction_reason_description(m->restriction_reasons), - get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret), + get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret, + skip_bot_commands), get_reply_markup_object(m->reply_markup)); } @@ -23107,6 +23206,8 @@ MessagesManager::Message *MessagesManager::get_message_to_send( : get_next_yet_unsent_message_id(d); LOG(INFO) << "Create " << message_id << " in " << dialog_id; + d->was_opened = true; + auto dialog_type = dialog_id.get_type(); auto my_id = td_->contacts_manager_->get_my_id(); @@ -24229,8 +24330,8 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { << file_view.has_alive_remote_location() << " " << file_view.has_active_upload_remote_location() << " " << file_view.has_active_download_remote_location() << " " << file_view.is_encrypted() << " " << is_web << " " << file_view.has_url() << " " - << to_string( - get_message_content_object(m->content.get(), td_, dialog_id, m->date, m->is_content_secret)); + << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date, + m->is_content_secret, false)); } auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group"); int32 input_single_media_flags = 0; @@ -24868,6 +24969,18 @@ bool MessagesManager::can_resend_message(const Message *m) const { return true; } +bool MessagesManager::is_group_dialog(DialogId dialog_id) const { + switch (dialog_id.get_type()) { + case DialogType::Chat: + return true; + case DialogType::Channel: + return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == + ContactsManager::ChannelType::Megagroup; + default: + return false; + } +} + bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const { if (dialog_id.get_type() != DialogType::Channel) { return false; @@ -28302,16 +28415,23 @@ void MessagesManager::send_update_message_send_succeeded(Dialog *d, MessageId ol make_tl_object(get_message_object(d->dialog_id, m), old_message_id.get())); } -void MessagesManager::send_update_message_content(DialogId dialog_id, MessageId message_id, - const MessageContent *content, int32 message_date, - bool is_content_secret, const char *source) const { - LOG(INFO) << "Send updateMessageContent for " << message_id << " in " << dialog_id << " from " << source; +void MessagesManager::send_update_message_content(DialogId dialog_id, const Message *m, const char *source) { + CHECK(m != nullptr); LOG_CHECK(have_dialog(dialog_id)) << "Send updateMessageContent in unknown " << dialog_id << " from " << source << " with load count " << loaded_dialogs_.count(dialog_id); - auto content_object = get_message_content_object(content, td_, dialog_id, message_date, is_content_secret); - send_closure( - G()->td(), &Td::send_update, - td_api::make_object(dialog_id.get(), message_id.get(), std::move(content_object))); + delete_bot_command_message_id(dialog_id, m->message_id); + try_add_bot_command_message_id(dialog_id, m); + send_update_message_content_impl(dialog_id, m, source); +} + +void MessagesManager::send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const { + CHECK(m != nullptr); + LOG(INFO) << "Send updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source; + auto content_object = get_message_content_object(m->content.get(), td_, dialog_id, m->is_failed_to_send ? 0 : m->date, + m->is_content_secret, need_skip_bot_commands(dialog_id, m)); + send_closure(G()->td(), &Td::send_update, + td_api::make_object(dialog_id.get(), m->message_id.get(), + std::move(content_object))); } void MessagesManager::send_update_message_edited(DialogId dialog_id, const Message *m) { @@ -28844,8 +28964,11 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI // } if (merge_message_content_file_id(td_, sent_message->content.get(), new_file_id)) { - send_update_message_content(dialog_id, old_message_id, sent_message->content.get(), sent_message->date, - sent_message->is_content_secret, source); + send_update_message_content(dialog_id, sent_message.get(), source); + } + + if (old_message_id.is_valid() && new_message_id < old_message_id) { + LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << new_message_id; } set_message_id(sent_message, new_message_id); @@ -28856,11 +28979,18 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI bool need_update = true; Message *m = add_message_to_dialog(d, std::move(sent_message), true, &need_update, &need_update_dialog_pos, source); - LOG_CHECK(m != nullptr) << td_->contacts_manager_->get_my_id() << " " << dialog_id << " " << old_message_id << " " - << new_message_id << " " << d->last_clear_history_message_id << " " - << d->max_unavailable_message_id << " " << d->last_message_id << " " << d->last_new_message_id - << " " << d->last_assigned_message_id << " " << have_input_peer(dialog_id, AccessRights::Read) - << " " << debug_add_message_to_dialog_fail_reason_ << " " << source; + if (m == nullptr) { + if (old_message_id.is_valid() && new_message_id < old_message_id) { + // the message ID has decreased. This could happen if some messages were lost. + // In this case the failure is possible + return {}; + } + LOG(FATAL) << td_->contacts_manager_->get_my_id() << " " << dialog_id << " " << old_message_id << " " + << new_message_id << " " << d->last_clear_history_message_id << " " << d->max_unavailable_message_id + << " " << d->last_message_id << " " << d->last_new_message_id << " " << d->last_assigned_message_id + << " " << have_input_peer(dialog_id, AccessRights::Read) << " " + << debug_add_message_to_dialog_fail_reason_ << " " << source; + } send_update_message_send_succeeded(d, old_message_id, m); if (need_update_dialog_pos) { @@ -30020,14 +30150,43 @@ void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector } auto d = from_database ? get_dialog(dialog_id) : get_dialog_force(dialog_id, "on_dialog_bots_updated"); - if (d == nullptr || d->reply_markup_message_id == MessageId()) { + if (d == nullptr) { return; } - const Message *m = get_message_force(d, d->reply_markup_message_id, "on_dialog_bots_updated"); - if (m == nullptr || (m->sender_user_id.is_valid() && !td::contains(bot_user_ids, m->sender_user_id))) { - LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot " - << (m == nullptr ? UserId() : m->sender_user_id) << " isn't a member of the chat"; - set_dialog_reply_markup(d, MessageId()); + + bool has_bots = !bot_user_ids.empty(); + if (!d->is_has_bots_inited || d->has_bots != has_bots) { + set_dialog_has_bots(d, has_bots); + on_dialog_updated(dialog_id, "on_dialog_bots_updated"); + } + + if (d->reply_markup_message_id != MessageId()) { + const Message *m = get_message_force(d, d->reply_markup_message_id, "on_dialog_bots_updated"); + if (m == nullptr || (m->sender_user_id.is_valid() && !td::contains(bot_user_ids, m->sender_user_id))) { + LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot " + << (m == nullptr ? UserId() : m->sender_user_id) << " isn't a member of the chat"; + set_dialog_reply_markup(d, MessageId()); + } + } +} + +void MessagesManager::set_dialog_has_bots(Dialog *d, bool has_bots) { + CHECK(d != nullptr); + LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_has_bots"; + + LOG(INFO) << "Set " << d->dialog_id << " has_bots to " << has_bots; + + auto old_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr); + d->has_bots = has_bots; + d->is_has_bots_inited = true; + auto new_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr); + if (old_skip_bot_commands != new_skip_bot_commands) { + auto it = dialog_bot_command_message_ids_.find(d->dialog_id); + if (it != dialog_bot_command_message_ids_.end()) { + for (auto message_id : it->second.message_ids) { + send_update_message_content_impl(d->dialog_id, get_message(d, message_id), "set_dialog_has_bots"); + } + } } } @@ -30114,7 +30273,7 @@ void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool if (!dialog_filters_.empty() && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); td_->contacts_manager_->for_each_secret_chat_with_user( - d->dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { + dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) { @@ -30122,6 +30281,18 @@ void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool } }); } + + if (is_deleted && d->has_bots) { + set_dialog_has_bots(d, false); + td_->contacts_manager_->for_each_secret_chat_with_user( + dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { + DialogId dialog_id(secret_chat_id); + auto d = get_dialog(dialog_id); // must not create the dialog + if (d != nullptr && d->is_update_new_chat_sent && d->has_bots) { + set_dialog_has_bots(d, false); + } + }); + } } } @@ -30632,8 +30803,8 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { auto file_id = get_message_content_upload_file_id(m->content.get()); if (!file_id.is_valid()) { LOG(ERROR) << "Have no file in " - << to_string( - get_message_content_object(m->content.get(), td_, dialog_id, m->date, m->is_content_secret)); + << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date, m->is_content_secret, + false)); return; } auto file_view = td_->file_manager_->get_file_view(file_id); @@ -32034,9 +32205,9 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } } + auto dialog_type = dialog_id.get_type(); if (message->sender_user_id == ContactsManager::get_anonymous_bot_user_id() && - !message->sender_dialog_id.is_valid() && dialog_id.get_type() == DialogType::Channel && - !is_broadcast_channel(dialog_id)) { + !message->sender_dialog_id.is_valid() && dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id)) { message->sender_user_id = UserId(); message->sender_dialog_id = dialog_id; } @@ -32096,7 +32267,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (from_update) { CHECK(message->have_next); CHECK(message->have_previous); - if (message_id <= d->last_new_message_id && dialog_id.get_type() != DialogType::Channel) { + if (message_id <= d->last_new_message_id && dialog_type != DialogType::Channel) { if (!G()->parameters().use_message_db) { if (td_->auth_manager_->is_bot() && Time::now() > start_time_ + 300 && MessageId(ServerMessageId(100)) <= message_id && message_id <= MessageId(ServerMessageId(1000)) && @@ -32148,7 +32319,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq return nullptr; } } - if ((message_id.is_server() || (message_id.is_local() && dialog_id.get_type() == DialogType::SecretChat)) && + if ((message_id.is_server() || (message_id.is_local() && dialog_type == DialogType::SecretChat)) && message_id <= d->max_unavailable_message_id) { LOG(INFO) << "Can't add an unavailable " << message_id << " to " << dialog_id << " from " << source; if (message->from_database) { @@ -32373,7 +32544,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (message->ttl > 0 && message->ttl_expires_at != 0) { auto now = Time::now(); if (message->ttl_expires_at <= now) { - if (dialog_id.get_type() == DialogType::SecretChat) { + if (dialog_type == DialogType::SecretChat) { LOG(INFO) << "Can't add " << message_id << " with expired TTL to " << dialog_id << " from " << source; delete_message_from_database(d, message_id, message.get(), true); debug_add_message_to_dialog_fail_reason_ = "delete expired by TTL message"; @@ -32391,7 +32562,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } if (message->ttl_period > 0) { - CHECK(dialog_id.get_type() != DialogType::SecretChat); + CHECK(dialog_type != DialogType::SecretChat); auto server_time = G()->server_time(); if (message->date + message->ttl_period <= server_time) { LOG(INFO) << "Can't add " << message_id << " with expired TTL period to " << dialog_id << " from " << source; @@ -32410,7 +32581,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq *need_update_dialog_pos = true; } - if (dialog_id.get_type() == DialogType::Channel && !message->contains_unread_mention) { + if (dialog_type == DialogType::Channel && !message->contains_unread_mention) { auto channel_read_media_period = G()->shared_config().get_option_integer("channels_read_media_period", (G()->is_test_dc() ? 300 : 7 * 86400)); if (message->date < G()->unix_time_cached() - channel_read_media_period) { @@ -32447,7 +32618,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } if (from_update && message_id > d->last_new_message_id && !message_id.is_yet_unsent()) { - if (dialog_id.get_type() == DialogType::SecretChat || message_id.is_server()) { + if (dialog_type == DialogType::SecretChat || message_id.is_server()) { // can delete messages, therefore must be called before message attaching/adding set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog"); } @@ -32602,7 +32773,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq add_message_to_database(d, m, "add_message_to_dialog"); } - if (from_update && dialog_id.get_type() == DialogType::Channel) { + if (from_update && dialog_type == DialogType::Channel) { auto now = max(G()->unix_time_cached(), m->date); if (m->date < now - 2 * 86400 && Slice(source) == Slice("updateNewChannelMessage")) { // if the message is pretty old, we might have missed the update that the message has already been read @@ -32675,7 +32846,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq try_hide_distance(dialog_id, m); if (!td_->auth_manager_->is_bot() && d->messages == nullptr && !m->is_outgoing && dialog_id != get_my_dialog_id()) { - switch (dialog_id.get_type()) { + switch (dialog_type) { case DialogType::User: td_->contacts_manager_->invalidate_user_full(dialog_id.get_user_id()); td_->contacts_manager_->reload_user_full(dialog_id.get_user_id()); @@ -32722,7 +32893,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq CHECK(is_inserted); } - switch (dialog_id.get_type()) { + switch (dialog_type) { case DialogType::User: case DialogType::Chat: if (m->message_id.is_server()) { @@ -32746,6 +32917,8 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq add_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id); } + try_add_bot_command_message_id(dialog_id, m); + result_message->debug_source = source; d->being_added_message_id = MessageId(); @@ -33754,8 +33927,7 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me } if (need_update && need_send_update_message_content) { - send_update_message_content(dialog_id, old_message->message_id, old_content.get(), old_message->date, - old_message->is_content_secret, "update_message_content"); + send_update_message_content(dialog_id, old_message, "update_message_content"); } return is_content_changed || need_update; } @@ -33934,6 +34106,9 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, d->last_read_inbox_message_id = d->last_new_message_id; d->last_read_outbox_message_id = d->last_new_message_id; } + d->has_bots = dialog_id.get_user_id() != ContactsManager::get_replies_bot_user_id() && + td_->contacts_manager_->is_user_bot(dialog_id.get_user_id()); + d->is_has_bots_inited = true; break; case DialogType::Chat: d->is_is_blocked_inited = true; @@ -33956,7 +34131,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, } case DialogType::SecretChat: if (d->last_new_message_id.get() <= MessageId::min().get()) { - LOG(INFO) << "Set " << d->dialog_id << " last new message in add_new_dialog from " << source; + LOG(INFO) << "Set " << dialog_id << " last new message in add_new_dialog from " << source; d->last_new_message_id = MessageId::min().get_next_message_id(MessageType::Local); } @@ -33964,7 +34139,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, d->notification_settings.use_default_show_preview = true; d->notification_settings.show_preview = false; d->notification_settings.is_secret_chat_show_preview_fixed = true; - on_dialog_updated(d->dialog_id, "fix secret chat show preview"); + on_dialog_updated(dialog_id, "fix secret chat show preview"); } d->have_full_history = true; @@ -33980,6 +34155,9 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, d->message_ttl_setting = MessageTtlSetting(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id())); d->is_message_ttl_setting_inited = true; + d->has_bots = td_->contacts_manager_->is_user_bot( + td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); + d->is_has_bots_inited = true; break; case DialogType::None: @@ -34008,10 +34186,10 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, } if (d->message_notification_group.group_id.is_valid()) { - notification_group_id_to_dialog_id_.emplace(d->message_notification_group.group_id, d->dialog_id); + notification_group_id_to_dialog_id_.emplace(d->message_notification_group.group_id, dialog_id); } if (d->mention_notification_group.group_id.is_valid()) { - notification_group_id_to_dialog_id_.emplace(d->mention_notification_group.group_id, d->dialog_id); + notification_group_id_to_dialog_id_.emplace(d->mention_notification_group.group_id, dialog_id); } if (pending_dialog_group_call_updates_.count(dialog_id) > 0) { auto it = pending_dialog_group_call_updates_.find(dialog_id); @@ -34079,6 +34257,10 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab // asynchronously get is_blocked from the server get_dialog_info_full(dialog_id, Auto()); } + if (being_added_dialog_id_ != dialog_id && !d->is_has_bots_inited && !td_->auth_manager_->is_bot()) { + // asynchronously get has_bots from the server + get_dialog_info_full(dialog_id, Auto()); + } if (being_added_dialog_id_ != dialog_id && !d->is_last_pinned_message_id_inited && !td_->auth_manager_->is_bot()) { // asynchronously get dialog pinned message from the server get_dialog_pinned_message(dialog_id, Auto()); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 9bdad9acb..87ba6dc4f 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -582,7 +582,8 @@ class MessagesManager final : public Actor { bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds); - Result> get_message_link(FullMessageId full_message_id, bool for_group, bool for_comment); + Result> get_message_link(FullMessageId full_message_id, int32 media_timestamp, bool for_group, + bool for_comment); string get_message_embedding_code(FullMessageId full_message_id, bool for_group, Promise &&promise); @@ -1235,6 +1236,8 @@ class MessagesManager final : public Actor { bool is_group_call_empty = false; bool is_message_ttl_setting_inited = false; bool has_expected_active_group_call_id = false; + bool has_bots = false; + bool is_has_bots_inited = false; bool increment_view_counter = false; @@ -2189,10 +2192,13 @@ class MessagesManager final : public Actor { void remove_message_dialog_notifications(Dialog *d, MessageId max_message_id, bool from_mentions, const char *source); + bool need_skip_bot_commands(DialogId dialog_id, const Message *m) const; + void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const; - void send_update_message_content(DialogId dialog_id, MessageId message_id, const MessageContent *content, - int32 message_date, bool is_content_secret, const char *source) const; + void send_update_message_content(DialogId dialog_id, const Message *m, const char *source); + + void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const; void send_update_message_edited(DialogId dialog_id, const Message *m); @@ -2324,6 +2330,8 @@ class MessagesManager final : public Actor { void set_dialog_is_blocked(Dialog *d, bool is_blocked); + void set_dialog_has_bots(Dialog *d, bool has_bots); + void set_dialog_last_pinned_message_id(Dialog *d, MessageId last_pinned_message_id); void drop_dialog_last_pinned_message_id(Dialog *d); @@ -2676,6 +2684,10 @@ class MessagesManager final : public Actor { void on_message_live_location_viewed_on_server(int64 task_id); + void try_add_bot_command_message_id(DialogId dialog_id, const Message *m); + + void delete_bot_command_message_id(DialogId dialog_id, MessageId message_id); + void add_message_file_sources(DialogId dialog_id, const Message *m); void remove_message_file_sources(DialogId dialog_id, const Message *m); @@ -2965,6 +2977,8 @@ class MessagesManager final : public Actor { void suffix_load_till_date(Dialog *d, int32 date, Promise<> promise); void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise); + bool is_group_dialog(DialogId dialog_id) const; + bool is_broadcast_channel(DialogId dialog_id) const; bool is_deleted_secret_chat(const Dialog *d) const; @@ -3299,6 +3313,11 @@ class MessagesManager final : public Actor { std::unordered_map, DialogIdHash> pending_add_default_join_group_call_as_dialog_id_; // dialog_id -> dependent dialogs + struct MessageIds { + std::unordered_set message_ids; + }; + std::unordered_map dialog_bot_command_message_ids_; + struct CallsDbState { std::array first_calls_database_message_id_by_index; std::array message_count_by_index; diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index d7a6f5c24..ceb06c041 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -574,7 +574,8 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co if (poll->is_quiz) { auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id; poll_type = td_api::make_object( - correct_option_id, get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation)); + correct_option_id, + get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true)); } else { poll_type = td_api::make_object(poll->allow_multiple_answers); } diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 64a87ee63..16ab45ff7 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -489,7 +489,7 @@ class GetDeepLinkInfoQuery final : public Td::ResultHandler { } FormattedText text{std::move(info->message_), std::move(entities)}; return promise_.set_value( - td_api::make_object(get_formatted_text_object(text), need_update)); + td_api::make_object(get_formatted_text_object(text, true), need_update)); } default: UNREACHABLE(); @@ -5161,8 +5161,9 @@ void Td::on_request(uint64 id, const td_api::getMessageThread &request) { } void Td::on_request(uint64 id, const td_api::getMessageLink &request) { - auto r_message_link = messages_manager_->get_message_link( - {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.for_album_, request.for_comment_); + auto r_message_link = + messages_manager_->get_message_link({DialogId(request.chat_id_), MessageId(request.message_id_)}, + request.media_timestamp_, request.for_album_, request.for_comment_); if (r_message_link.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_message_link.move_as_error()); } else { @@ -6741,6 +6742,14 @@ void Td::on_request(uint64 id, const td_api::cancelDownloadFile &request) { send_closure(actor_id(this), &Td::send_result, id, make_tl_object()); } +void Td::on_request(uint64 id, const td_api::getSuggestedFileName &request) { + Result r_file_name = file_manager_->get_suggested_file_name(FileId(request.file_id_, 0), request.directory_); + if (r_file_name.is_error()) { + return send_closure(actor_id(this), &Td::send_error, id, r_file_name.move_as_error()); + } + send_closure(actor_id(this), &Td::send_result, id, td_api::make_object(r_file_name.ok())); +} + void Td::on_request(uint64 id, td_api::uploadFile &request) { auto priority = request.priority_; if (!(1 <= priority && priority <= 32)) { @@ -8490,7 +8499,7 @@ td_api::object_ptr Td::do_static_request(const td_api::getTextEn return make_error(400, "Text must be encoded in UTF-8"); } auto text_entities = find_entities(request.text_, false); - return make_tl_object(get_text_entities_object(text_entities)); + return make_tl_object(get_text_entities_object(text_entities, false)); } td_api::object_ptr Td::do_static_request(td_api::parseTextEntities &request) { @@ -8524,7 +8533,8 @@ td_api::object_ptr Td::do_static_request(td_api::parseTextEntiti return make_error(400, PSLICE() << "Can't parse entities: " << r_entities.error().message()); } - return make_tl_object(std::move(request.text_), get_text_entities_object(r_entities.ok())); + return make_tl_object(std::move(request.text_), + get_text_entities_object(r_entities.ok(), false)); } td_api::object_ptr Td::do_static_request(td_api::parseMarkdown &request) { @@ -8544,7 +8554,7 @@ td_api::object_ptr Td::do_static_request(td_api::parseMarkdown & auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)}); fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).ensure(); - return get_formatted_text_object(parsed_text); + return get_formatted_text_object(parsed_text, true); } td_api::object_ptr Td::do_static_request(td_api::getMarkdownText &request) { @@ -8562,7 +8572,7 @@ td_api::object_ptr Td::do_static_request(td_api::getMarkdownText return make_error(400, status.error().message()); } - return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)})); + return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), true); } td_api::object_ptr Td::do_static_request(const td_api::getFileMimeType &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 758d1fd0d..1b251ea0d 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -248,7 +248,7 @@ class Td final : public NetQueryCallback { static td_api::object_ptr static_request(td_api::object_ptr function); private: - static constexpr const char *TDLIB_VERSION = "1.7.5"; + static constexpr const char *TDLIB_VERSION = "1.7.6"; static constexpr int64 ONLINE_ALARM_ID = 0; static constexpr int64 PING_SERVER_ALARM_ID = -1; static constexpr int32 PING_SERVER_TIMEOUT = 300; @@ -892,6 +892,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::cancelDownloadFile &request); + void on_request(uint64 id, const td_api::getSuggestedFileName &request); + void on_request(uint64 id, td_api::uploadFile &request); void on_request(uint64 id, const td_api::cancelUploadFile &request); diff --git a/td/telegram/TermsOfService.h b/td/telegram/TermsOfService.h index 4a6ed5f41..4c3cc4fcf 100644 --- a/td/telegram/TermsOfService.h +++ b/td/telegram/TermsOfService.h @@ -42,7 +42,8 @@ class TermsOfService { return nullptr; } - return td_api::make_object(get_formatted_text_object(text_), min_user_age_, show_popup_); + return td_api::make_object(get_formatted_text_object(text_, true), min_user_age_, + show_popup_); } template diff --git a/td/telegram/WebPagesManager.cpp b/td/telegram/WebPagesManager.cpp index d966cf373..9639b76b8 100644 --- a/td/telegram/WebPagesManager.cpp +++ b/td/telegram/WebPagesManager.cpp @@ -1296,7 +1296,7 @@ tl_object_ptr WebPagesManager::get_web_page_object(WebPageId we return make_tl_object( web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title, - get_formatted_text_object(description), get_photo_object(td_->file_manager_.get(), web_page->photo), + get_formatted_text_object(description, true), get_photo_object(td_->file_manager_.get(), web_page->photo), web_page->embed_url, web_page->embed_type, web_page->embed_dimensions.width, web_page->embed_dimensions.height, web_page->duration, web_page->author, web_page->document.type == Document::Type::Animation @@ -1751,6 +1751,14 @@ string WebPagesManager::get_web_page_search_text(WebPageId web_page_id) const { return PSTRING() << web_page->title + " " + web_page->description; } +int32 WebPagesManager::get_web_page_duration(WebPageId web_page_id) const { + const WebPage *web_page = get_web_page(web_page_id); + if (web_page == nullptr) { + return 0; + } + return web_page->duration; +} + vector WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const { if (web_page == nullptr) { return vector(); diff --git a/td/telegram/WebPagesManager.h b/td/telegram/WebPagesManager.h index e35043634..fffacf10b 100644 --- a/td/telegram/WebPagesManager.h +++ b/td/telegram/WebPagesManager.h @@ -91,6 +91,8 @@ class WebPagesManager final : public Actor { string get_web_page_search_text(WebPageId web_page_id) const; + int32 get_web_page_duration(WebPageId web_page_id) const; + private: static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1 << 0; static constexpr int32 WEBPAGE_FLAG_HAS_SITE_NAME = 1 << 1; diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 0a41a9985..1f427195f 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2607,11 +2607,12 @@ class CliClient final : public Actor { } else if (op == "gmlink") { string chat_id; string message_id; + int32 media_timestamp; bool for_album; bool for_comment; - get_args(args, chat_id, message_id, for_album, for_comment); + get_args(args, chat_id, message_id, media_timestamp, for_album, for_comment); send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id), - for_album, for_comment)); + media_timestamp, for_album, for_comment)); } else if (op == "gmec") { string chat_id; string message_id; @@ -2669,6 +2670,11 @@ class CliClient final : public Actor { } } else if (op == "cdf") { send_request(td_api::make_object(as_file_id(args), false)); + } else if (op == "gsfn") { + string file_id; + string directory_name; + get_args(args, file_id, directory_name); + send_request(td_api::make_object(as_file_id(file_id), directory_name)); } else if (op == "uf" || op == "ufs" || op == "ufse") { string file_path; int32 priority; diff --git a/td/telegram/files/FileLoaderUtils.cpp b/td/telegram/files/FileLoaderUtils.cpp index 32438ab29..c30c728b5 100644 --- a/td/telegram/files/FileLoaderUtils.cpp +++ b/td/telegram/files/FileLoaderUtils.cpp @@ -16,6 +16,7 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" +#include "td/utils/port/Clocks.h" #include "td/utils/port/FileFd.h" #include "td/utils/port/path.h" #include "td/utils/Random.h" @@ -29,13 +30,13 @@ namespace td { int VERBOSITY_NAME(file_loader) = VERBOSITY_NAME(DEBUG) + 2; namespace { -Result> try_create_new_file(Result result_name) { - TRY_RESULT(name, std::move(result_name)); +Result> try_create_new_file(CSlice name) { + LOG(DEBUG) << "Trying to create new file " << name; TRY_RESULT(fd, FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640)); return std::make_pair(std::move(fd), name.str()); } -Result> try_open_file(Result result_name) { - TRY_RESULT(name, std::move(result_name)); +Result> try_open_file(CSlice name) { + LOG(DEBUG) << "Trying to open file " << name; TRY_RESULT(fd, FileFd::open(name, FileFd::Read, 0640)); return std::make_pair(std::move(fd), name.str()); } @@ -76,33 +77,26 @@ Result> open_temp_file(FileType file_type) { template bool for_suggested_file_name(CSlice name, bool use_pmc, bool use_random, F &&callback) { - auto try_callback = [&](Result r_path) { - if (r_path.is_error()) { - return true; - } - LOG(DEBUG) << "Trying " << r_path.ok(); - return callback(r_path.move_as_ok()); - }; auto cleaned_name = clean_filename(name); PathView path_view(cleaned_name); auto stem = path_view.file_stem(); auto ext = path_view.extension(); bool active = true; if (!stem.empty() && !G()->parameters().ignore_file_names) { - active = try_callback(PSLICE() << stem << Ext{ext}); + active = callback(PSLICE() << stem << Ext{ext}); for (int i = 0; active && i < 10; i++) { - active = try_callback(PSLICE() << stem << "_(" << i << ")" << Ext{ext}); + active = callback(PSLICE() << stem << "_(" << i << ")" << Ext{ext}); } for (int i = 2; active && i < 12 && use_random; i++) { - active = try_callback(PSLICE() << stem << "_(" << RandSuff{i} << ")" << Ext{ext}); + active = callback(PSLICE() << stem << "_(" << RandSuff{i} << ")" << Ext{ext}); } } else if (use_pmc) { auto pmc = G()->td_db()->get_binlog_pmc(); int32 file_id = to_integer(pmc->get("perm_file_id")); pmc->set("perm_file_id", to_string(file_id + 1)); - active = try_callback(PSLICE() << "file_" << file_id << Ext{ext}); + active = callback(PSLICE() << "file_" << file_id << Ext{ext}); if (active) { - active = try_callback(PSLICE() << "file_" << file_id << "_" << RandSuff{6} << Ext{ext}); + active = callback(PSLICE() << "file_" << file_id << "_" << RandSuff{6} << Ext{ext}); } } return active; @@ -144,6 +138,51 @@ Result search_file(CSlice dir, CSlice name, int64 expected_size) { return res; } +Result get_suggested_file_name(CSlice directory, Slice file_name) { + string cleaned_name = clean_filename(file_name.str()); + file_name = cleaned_name; + + if (directory.empty()) { + directory = "./"; + } + + auto dir_stat = stat(directory); + if (dir_stat.is_error() || !dir_stat.ok().is_dir_) { + return cleaned_name; + } + + PathView path_view(file_name); + auto stem = path_view.file_stem(); + auto ext = path_view.extension(); + + if (stem.empty()) { + return cleaned_name; + } + + Slice directory_slice = directory; + while (directory_slice.size() > 1 && (directory_slice.back() == '/' || directory_slice.back() == '\\')) { + directory_slice.remove_suffix(1); + } + + auto check_file_name = [directory_slice](Slice name) { + return stat(PSLICE() << directory_slice << TD_DIR_SLASH << name).is_error(); // in case of success, the name is bad + }; + + string checked_name = PSTRING() << stem << Ext{ext}; + if (check_file_name(checked_name)) { + return checked_name; + } + + for (int i = 1; i < 100; i++) { + checked_name = PSTRING() << stem << " (" << i << ")" << Ext{ext}; + if (check_file_name(checked_name)) { + return checked_name; + } + } + + return PSTRING() << stem << " - " << StringBuilder::FixedDouble(Clocks::system(), 3) << Ext{ext}; +} + Result save_file_bytes(FileType type, BufferSlice bytes, CSlice file_name) { auto r_old_path = search_file(get_files_dir(type), file_name, bytes.size()); if (r_old_path.is_ok()) { diff --git a/td/telegram/files/FileLoaderUtils.h b/td/telegram/files/FileLoaderUtils.h index 1a4fdda77..42332008e 100644 --- a/td/telegram/files/FileLoaderUtils.h +++ b/td/telegram/files/FileLoaderUtils.h @@ -28,6 +28,8 @@ Result create_from_temp(CSlice temp_path, CSlice dir, CSlice name) TD_WA Result search_file(CSlice dir, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT; +Result get_suggested_file_name(CSlice dir, Slice file_name) TD_WARN_UNUSED_RESULT; + Result save_file_bytes(FileType type, BufferSlice bytes, CSlice file_name); Slice get_files_base_dir(FileType file_type); diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index bee8dd49f..a634cd9d6 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -1497,7 +1497,14 @@ Result FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy return std::accumulate(node->file_ids_.begin(), node->file_ids_.end(), 0, [](const auto &x, const auto &y) { return x + (y.get_remote() != 0); }); }; - if (count_local(x_node) + count_local(y_node) > 100) { + auto x_local_file_ids = count_local(x_node); + auto y_local_file_ids = count_local(y_node); + if (x_local_file_ids + y_local_file_ids > 100) { + } + + if (y_node->file_ids_.size() >= 100 || x_node->file_ids_.size() >= 100) { + LOG(INFO) << "Merge files with " << x_local_file_ids << '/' << x_node->file_ids_.size() << " and " + << y_local_file_ids << '/' << y_node->file_ids_.size() << " file IDs"; } FileNodePtr nodes[] = {x_node, y_node, x_node}; @@ -1662,8 +1669,7 @@ Result FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy for (auto file_id : other_node->file_ids_) { auto file_id_info = get_file_id_info(file_id); - LOG_CHECK(file_id_info->node_id_ == node_ids[other_node_i]) - << node_ids[node_i] << " " << node_ids[other_node_i] << " " << file_id << " " << file_id_info->node_id_; + CHECK(file_id_info->node_id_ == node_ids[other_node_i]); file_id_info->node_id_ = node_ids[node_i]; send_updates_flag |= file_id_info->send_updates_flag_; } @@ -1675,7 +1681,7 @@ Result FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy node->on_info_changed(); } - // Check is some download/upload queries are ready + // Check if some download/upload queries are ready for (auto file_id : vector(node->file_ids_)) { auto *info = get_file_id_info(file_id); if (info->download_priority_ != 0 && file_view.has_local_location()) { @@ -3069,6 +3075,11 @@ Result FileManager::check_input_file_id(FileType type, Result re if (!file_view.has_remote_location()) { // TODO why not return file_id here? We will dup it anyway // But it will not be duped if has_input_media(), so for now we can't return main_file_id + + if (file_view.has_url() && !is_encrypted) { + // URLs in non-secret chats never needs to be reuploaded, so they don't need to be duped + return file_node->main_file_id_; + } return dup_file_id(file_id); } @@ -3139,7 +3150,7 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr string hash; if (G()->shared_config().get_option_boolean("reuse_uploaded_photos_by_hash") && new_type == FileType::Photo) { auto r_stat = stat(path); - if (r_stat.is_ok() && r_stat.ok().size_ > 0 && r_stat.ok().size_ < 5000000) { + if (r_stat.is_ok() && r_stat.ok().size_ > 0 && r_stat.ok().size_ < 11000000) { auto r_file_content = read_file_str(path, r_stat.ok().size_); if (r_file_content.is_ok()) { hash = sha256(r_file_content.ok()); @@ -3875,6 +3886,18 @@ FullRemoteFileLocation *FileManager::get_remote(int32 key) { return &remote_location_info_.get(key).remote_; } +Result FileManager::get_suggested_file_name(FileId file_id, const string &directory) { + if (!file_id.is_valid()) { + return Status::Error(400, "Invalid file identifier"); + } + auto node = get_sync_file_node(file_id); + if (!node) { + return Status::Error(400, "Wrong file identifier"); + } + + return ::td::get_suggested_file_name(directory, PathView(node->suggested_path()).file_name()); +} + void FileManager::hangup() { file_db_.reset(); file_generate_manager_.reset(); diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index 0c222624b..287a86ca9 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -463,6 +463,8 @@ class FileManager final : public FileLoadManager::Callback { void delete_file_reference(FileId file_id, std::string file_reference); void get_content(FileId file_id, Promise promise); + Result get_suggested_file_name(FileId file_id, const string &directory); + void read_file_part(FileId file_id, int32 offset, int32 count, int left_tries, Promise> promise); diff --git a/td/telegram/net/Session.cpp b/td/telegram/net/Session.cpp index 2a7bef7b5..cb3f335df 100644 --- a/td/telegram/net/Session.cpp +++ b/td/telegram/net/Session.cpp @@ -39,6 +39,7 @@ #include "td/utils/Time.h" #include "td/utils/Timer.h" #include "td/utils/tl_parsers.h" +#include "td/utils/utf8.h" #include #include @@ -768,22 +769,31 @@ Status Session::on_message_result_ok(uint64 id, BufferSlice packet, size_t origi return Status::OK(); } -void Session::on_message_result_error(uint64 id, int error_code, BufferSlice message) { +void Session::on_message_result_error(uint64 id, int error_code, string message) { + if (!check_utf8(message)) { + LOG(ERROR) << "Receive invalid error message \"" << message << '"'; + message = "INVALID_UTF8_ERROR_MESSAGE"; + } + if (error_code <= -10000 || error_code >= 10000 || error_code == 0) { + LOG(ERROR) << "Receive invalid error code " << error_code << " with message \"" << message << '"'; + error_code = 500; + } + // UNAUTHORIZED - if (error_code == 401 && message.as_slice() != CSlice("SESSION_PASSWORD_NEEDED")) { - if (auth_data_.use_pfs() && message.as_slice() == CSlice("AUTH_KEY_PERM_EMPTY")) { + if (error_code == 401 && message != "SESSION_PASSWORD_NEEDED") { + if (auth_data_.use_pfs() && message == CSlice("AUTH_KEY_PERM_EMPTY")) { LOG(INFO) << "Receive AUTH_KEY_PERM_EMPTY in session " << auth_data_.get_session_id() << " for auth key " << auth_data_.get_tmp_auth_key().id(); auth_data_.drop_tmp_auth_key(); on_tmp_auth_key_updated(); error_code = 500; } else { - if (message.as_slice() == CSlice("USER_DEACTIVATED_BAN")) { + if (message == "USER_DEACTIVATED_BAN") { LOG(PLAIN) << "Your account was suspended for suspicious activity. If you think that this is a mistake, please " "write to recover@telegram.org your phone number and other details to recover the account."; } auth_data_.set_auth_flag(false); - G()->shared_config().set_option_string("auth", message.as_slice().str()); + G()->shared_config().set_option_string("auth", message); shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key()); on_session_failed(Status::OK()); } @@ -796,10 +806,10 @@ void Session::on_message_result_error(uint64 id, int error_code, BufferSlice mes if (error_code < 0) { LOG(WARNING) << "Session::on_message_result_error from mtproto " << tag("id", id) << tag("error_code", error_code) - << tag("msg", message.as_slice()); + << tag("msg", message); } else { LOG(DEBUG) << "Session::on_message_result_error " << tag("id", id) << tag("error_code", error_code) - << tag("msg", message.as_slice()); + << tag("msg", message); } auto it = sent_queries_.find(id); if (it == sent_queries_.end()) { @@ -811,8 +821,7 @@ void Session::on_message_result_error(uint64 id, int error_code, BufferSlice mes cleanup_container(id, query_ptr); mark_as_known(id, query_ptr); - query_ptr->query->set_error(Status::Error(error_code, message.as_slice()), - current_info_->connection->get_name().str()); + query_ptr->query->set_error(Status::Error(error_code, message), current_info_->connection->get_name().str()); query_ptr->query->set_message_id(0); query_ptr->query->cancel_slot_.clear_event(); return_query(std::move(query_ptr->query)); diff --git a/td/telegram/net/Session.h b/td/telegram/net/Session.h index 32776fd6d..022aafff4 100644 --- a/td/telegram/net/Session.h +++ b/td/telegram/net/Session.h @@ -208,7 +208,7 @@ class Session final void on_message_ack(uint64 id) final; Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) final; - void on_message_result_error(uint64 id, int error_code, BufferSlice message) final; + void on_message_result_error(uint64 id, int error_code, string message) final; void on_message_failed(uint64 id, Status status) final; void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) final; diff --git a/tddb/td/db/binlog/Binlog.cpp b/tddb/td/db/binlog/Binlog.cpp index 123008aea..fb2e4bcab 100644 --- a/tddb/td/db/binlog/Binlog.cpp +++ b/tddb/td/db/binlog/Binlog.cpp @@ -314,8 +314,8 @@ Status Binlog::close_and_destroy() { } Status Binlog::destroy(Slice path) { + unlink(PSLICE() << path << ".new").ignore(); // delete regenerated version first to avoid it becoming main version unlink(PSLICE() << path).ignore(); - unlink(PSLICE() << path << ".new").ignore(); return Status::OK(); } diff --git a/tdutils/td/utils/StringBuilder.h b/tdutils/td/utils/StringBuilder.h index 1d7da54ff..1a1987caa 100644 --- a/tdutils/td/utils/StringBuilder.h +++ b/tdutils/td/utils/StringBuilder.h @@ -27,6 +27,11 @@ class StringBuilder { error_flag_ = false; } + void pop_back() { + CHECK(current_ptr_ > begin_ptr_); + current_ptr_--; + } + MutableCSlice as_cslice() { if (current_ptr_ >= end_ptr_ + RESERVED_SIZE) { std::abort(); // shouldn't happen