Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2021-07-27 23:35:50 +02:00
commit 6b93b00f6a
46 changed files with 673 additions and 223 deletions

View File

@ -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 "")

View File

@ -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).

View File

@ -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.

View File

@ -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)

View File

@ -1,6 +1,6 @@
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
<Identity Id="Telegram.Td.UWP" Version="1.7.5" Language="en-US" Publisher="Telegram LLC" />
<Identity Id="Telegram.Td.UWP" Version="1.7.6" Language="en-US" Publisher="Telegram LLC" />
<DisplayName>TDLib for Universal Windows Platform</DisplayName>
<Description>TDLib is a library for building Telegram clients</Description>
<MoreInfo>https://core.telegram.org/tdlib</MoreInfo>

View File

@ -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;

View File

@ -47,12 +47,20 @@ static Result<typename T::ReturnType> 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());

View File

@ -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;

View File

@ -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) {

View File

@ -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<AuthKeyHandshake> handshake, unique_ptr<RawConnection> raw_connection,
@ -36,26 +36,19 @@ class HandshakeActor final : public Actor {
Promise<unique_ptr<AuthKeyHandshake>> 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();
};

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -184,13 +184,11 @@ void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&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(

View File

@ -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<td_api::updateCall>(
make_tl_object<td_api::call>(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<td_api::updateCall>(make_tl_object<td_api::call>(
local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_user_id_.get(), is_outgoing_,
is_video_, call_state_.get_call_state_object())));
}
}

View File

@ -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};

View File

@ -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;

View File

@ -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<Unit>());
}
}
@ -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<Unit>(), "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<Unit>(), "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)) {

View File

@ -91,9 +91,9 @@ const FormattedText &Game::get_text() const {
return text_;
}
tl_object_ptr<td_api::game> Game::get_game_object(Td *td) const {
tl_object_ptr<td_api::game> Game::get_game_object(Td *td, bool skip_bot_commands) const {
return make_tl_object<td_api::game>(
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"));
}

View File

@ -63,7 +63,7 @@ class Game {
const FormattedText &get_text() const;
tl_object_ptr<td_api::game> get_game_object(Td *td) const;
tl_object_ptr<td_api::game> get_game_object(Td *td, bool skip_bot_commands) const;
bool has_input_media() const;

View File

@ -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,

View File

@ -39,7 +39,7 @@ Result<InputMessageText> 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<InputMessageText> process_input_message_text(const ContactsManager *conta
}
td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text) {
return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text),
return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false),
input_message_text.disable_web_page_preview,
input_message_text.clear_draft);
}

View File

@ -191,7 +191,8 @@ class LinkManager::InternalLinkMessageDraft final : public InternalLink {
bool contains_link_ = false;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_), contains_link_);
return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true),
contains_link_);
}
public:
@ -1124,6 +1125,7 @@ Result<MessageLinkInfo> 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<MessageLinkInfo> 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<MessageLinkInfo> 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<MessageLinkInfo> 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();

View File

@ -2810,6 +2810,19 @@ void remove_message_content_web_page(MessageContent *content) {
static_cast<MessageText *>(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<int32> &&option_ids, Promise<Unit> &&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<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> 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<const MessageAnimation *>(content);
return make_tl_object<td_api::messageAnimation>(
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<const MessageAudio *>(content);
return make_tl_object<td_api::messageAudio>(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<const MessageContact *>(content);
@ -4656,11 +4669,11 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
const MessageDocument *m = static_cast<const MessageDocument *>(content);
return make_tl_object<td_api::messageDocument>(
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<const MessageGame *>(content);
return make_tl_object<td_api::messageGame>(m->game.get_game_object(td));
return make_tl_object<td_api::messageGame>(m->game.get_game_object(td, skip_bot_commands));
}
case MessageContentType::Invoice: {
const MessageInvoice *m = static_cast<const MessageInvoice *>(content);
@ -4682,7 +4695,8 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
case MessageContentType::Photo: {
const MessagePhoto *m = static_cast<const MessagePhoto *>(content);
return make_tl_object<td_api::messagePhoto>(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<const MessageSticker *>(content);
@ -4690,7 +4704,7 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::Text: {
const MessageText *m = static_cast<const MessageText *>(content);
return make_tl_object<td_api::messageText>(get_formatted_text_object(m->text),
return make_tl_object<td_api::messageText>(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<td_api::MessageContent> get_message_content_object(const MessageCo
case MessageContentType::Video: {
const MessageVideo *m = static_cast<const MessageVideo *>(content);
return make_tl_object<td_api::messageVideo>(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<const MessageVideoNote *>(content);
@ -4712,7 +4727,8 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
case MessageContentType::VoiceNote: {
const MessageVoiceNote *m = static_cast<const MessageVoiceNote *>(content);
return make_tl_object<td_api::messageVoiceNote>(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<const MessageChatCreate *>(content);
@ -4901,6 +4917,10 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td)
auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
return td->audios_manager_->get_audio_duration(audio_file_id);
}
case MessageContentType::Text: {
auto web_page_id = static_cast<const MessageText *>(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<const MessageVideo *>(content)->file_id;
return td->videos_manager_->get_video_duration(video_file_id);

View File

@ -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<int32> &&option_ids, Promise<Unit> &&promise);
@ -195,7 +197,7 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> 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);

View File

@ -140,11 +140,15 @@ tl_object_ptr<td_api::textEntity> MessageEntity::get_text_entity_object() const
return make_tl_object<td_api::textEntity>(offset, length, get_text_entity_type_object());
}
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities) {
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands) {
vector<tl_object_ptr<td_api::textEntity>> 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<td_api::formattedText> get_formatted_text_object(const FormattedText &text) {
return td_api::make_object<td_api::formattedText>(text.text, get_text_entities_object(text.entities));
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands) {
return td_api::make_object<td_api::formattedText>(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<FormattedText> 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;
}

View File

@ -131,9 +131,10 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
vector<tl_object_ptr<td_api::textEntity>> &&input_entities,
bool allow_all = false);
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities);
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands);
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text);
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands);
@ -191,6 +192,8 @@ Result<FormattedText> 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

View File

@ -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;

View File

@ -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_ptr<telegram_api:
bool is_content_secret = is_secret_message_content(ttl, content->get_type());
if ((update->flags_ & telegram_api::updateServiceNotification::POPUP_MASK) != 0) {
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateServiceNotification>(
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<td_api::updateServiceNotification>(
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<Unit> &&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<DialogId, unique_ptr<MessagesManager::Message>> 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, vector<tl_object_ptr<te
// TODO add is_blocked to telegram_api::dialog
get_dialog_info_full(dialog_id, Auto());
}
if (!d->is_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::Message> 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<std::pair<string, bool>> MessagesManager::get_message_link(FullMessageId full_message_id, bool for_group,
bool for_comment) {
Result<std::pair<string, bool>> 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<std::pair<string, bool>> 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<ExportChannelMessageLinkQuery>(Promise<Unit>())
->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<std::pair<string, bool>> 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<td_api::messageLinkInfo> 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<td_api::message> message;
int32 media_timestamp = 0;
bool for_album = false;
bool for_comment = false;
@ -17809,11 +17867,17 @@ td_api::object_ptr<td_api::messageLinkInfo> 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<td_api::messageLinkInfo>(is_public, dialog_id.get(), std::move(message), for_album,
for_comment);
return td_api::make_object<td_api::messageLinkInfo>(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<td_api::message> 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<td_api::message> 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<td_api::message>(
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<td_api::message> 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<td_api::updateMessageSendSucceeded>(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<td_api::updateMessageContent>(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<td_api::updateMessageContent>(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<UserId>
}
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<Dialog> &&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<Dialog> &&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<Dialog> &&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<Dialog> &&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<Dialog> &&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<Message> &&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());

View File

@ -582,7 +582,8 @@ class MessagesManager final : public Actor {
bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);
Result<std::pair<string, bool>> get_message_link(FullMessageId full_message_id, bool for_group, bool for_comment);
Result<std::pair<string, bool>> 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<Unit> &&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<DialogId, vector<DialogId>, DialogIdHash>
pending_add_default_join_group_call_as_dialog_id_; // dialog_id -> dependent dialogs
struct MessageIds {
std::unordered_set<MessageId, MessageIdHash> message_ids;
};
std::unordered_map<DialogId, MessageIds, DialogIdHash> dialog_bot_command_message_ids_;
struct CallsDbState {
std::array<MessageId, 2> first_calls_database_message_id_by_index;
std::array<int32, 2> message_count_by_index;

View File

@ -574,7 +574,8 @@ td_api::object_ptr<td_api::poll> 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<td_api::pollTypeQuiz>(
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<td_api::pollTypeRegular>(poll->allow_multiple_answers);
}

View File

@ -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<td_api::deepLinkInfo>(get_formatted_text_object(text), need_update));
td_api::make_object<td_api::deepLinkInfo>(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<td_api::ok>());
}
void Td::on_request(uint64 id, const td_api::getSuggestedFileName &request) {
Result<string> 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<td_api::text>(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_api::Object> 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<td_api::textEntities>(get_text_entities_object(text_entities));
return make_tl_object<td_api::textEntities>(get_text_entities_object(text_entities, false));
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntities &request) {
@ -8524,7 +8533,8 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntiti
return make_error(400, PSLICE() << "Can't parse entities: " << r_entities.error().message());
}
return make_tl_object<td_api::formattedText>(std::move(request.text_), get_text_entities_object(r_entities.ok()));
return make_tl_object<td_api::formattedText>(std::move(request.text_),
get_text_entities_object(r_entities.ok(), false));
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &request) {
@ -8544,7 +8554,7 @@ td_api::object_ptr<td_api::Object> 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_api::Object> Td::do_static_request(td_api::getMarkdownText &request) {
@ -8562,7 +8572,7 @@ td_api::object_ptr<td_api::Object> 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_api::Object> Td::do_static_request(const td_api::getFileMimeType &request) {

View File

@ -248,7 +248,7 @@ class Td final : public NetQueryCallback {
static td_api::object_ptr<td_api::Object> static_request(td_api::object_ptr<td_api::Function> 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);

View File

@ -42,7 +42,8 @@ class TermsOfService {
return nullptr;
}
return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_), min_user_age_, show_popup_);
return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true), min_user_age_,
show_popup_);
}
template <class StorerT>

View File

@ -1296,7 +1296,7 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
return make_tl_object<td_api::webPage>(
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<FileId> WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const {
if (web_page == nullptr) {
return vector<FileId>();

View File

@ -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;

View File

@ -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<td_api::getMessageLink>(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<td_api::cancelDownloadFile>(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<td_api::getSuggestedFileName>(as_file_id(file_id), directory_name));
} else if (op == "uf" || op == "ufs" || op == "ufse") {
string file_path;
int32 priority;

View File

@ -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<std::pair<FileFd, string>> try_create_new_file(Result<CSlice> result_name) {
TRY_RESULT(name, std::move(result_name));
Result<std::pair<FileFd, string>> 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<std::pair<FileFd, string>> try_open_file(Result<CSlice> result_name) {
TRY_RESULT(name, std::move(result_name));
Result<std::pair<FileFd, string>> 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<std::pair<FileFd, string>> open_temp_file(FileType file_type) {
template <class F>
bool for_suggested_file_name(CSlice name, bool use_pmc, bool use_random, F &&callback) {
auto try_callback = [&](Result<CSlice> 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<int32>(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<string> search_file(CSlice dir, CSlice name, int64 expected_size) {
return res;
}
Result<string> 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<FullLocalFileLocation> 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()) {

View File

@ -28,6 +28,8 @@ Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) TD_WA
Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT;
Result<string> get_suggested_file_name(CSlice dir, Slice file_name) TD_WARN_UNUSED_RESULT;
Result<FullLocalFileLocation> save_file_bytes(FileType type, BufferSlice bytes, CSlice file_name);
Slice get_files_base_dir(FileType file_type);

View File

@ -1497,7 +1497,14 @@ Result<FileId> 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<FileId> 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<FileId> 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<FileId>(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<FileId> FileManager::check_input_file_id(FileType type, Result<FileId> 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<FileId> 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<string> 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();

View File

@ -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<BufferSlice> promise);
Result<string> 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<td_api::object_ptr<td_api::filePart>> promise);

View File

@ -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 <tuple>
#include <utility>
@ -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));

View File

@ -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;

View File

@ -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();
}

View File

@ -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