tdlight/td/telegram/MessagesManager.cpp

41258 lines
1.6 MiB

//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/MessagesManager.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ChainId.h"
#include "td/telegram/ChannelType.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Dependencies.h"
#include "td/telegram/DialogActionBar.h"
#include "td/telegram/DialogDb.h"
#include "td/telegram/DialogFilter.h"
#include "td/telegram/DialogFilter.hpp"
#include "td/telegram/DialogLocation.h"
#include "td/telegram/DialogNotificationSettings.hpp"
#include "td/telegram/DownloadManager.h"
#include "td/telegram/DraftMessage.h"
#include "td/telegram/DraftMessage.hpp"
#include "td/telegram/FileReferenceManager.h"
#include "td/telegram/files/FileId.hpp"
#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/files/FileType.h"
#include "td/telegram/ForumTopicManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/GroupCallManager.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/InputMessageText.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/Location.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessageContent.h"
#include "td/telegram/MessageDb.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageEntity.hpp"
#include "td/telegram/MessageReaction.h"
#include "td/telegram/MessageReaction.hpp"
#include "td/telegram/MessageReplyInfo.hpp"
#include "td/telegram/MessageSender.h"
#include "td/telegram/misc.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/NotificationGroupType.h"
#include "td/telegram/NotificationManager.h"
#include "td/telegram/NotificationSettingsManager.h"
#include "td/telegram/NotificationSound.h"
#include "td/telegram/NotificationType.h"
#include "td/telegram/OptionManager.h"
#include "td/telegram/PollId.h"
#include "td/telegram/PublicDialogType.h"
#include "td/telegram/ReplyMarkup.h"
#include "td/telegram/ReplyMarkup.hpp"
#include "td/telegram/SecretChatsManager.h"
#include "td/telegram/SponsoredMessageManager.h"
#include "td/telegram/StickerType.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/TdParameters.h"
#include "td/telegram/TopDialogCategory.h"
#include "td/telegram/UpdatesManager.h"
#include "td/telegram/Version.h"
#include "td/telegram/WebPageId.h"
#include "td/db/binlog/BinlogEvent.h"
#include "td/db/binlog/BinlogHelper.h"
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueAsync.h"
#include "td/actor/SleepActor.h"
#include "td/utils/algorithm.h"
#include "td/utils/format.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/utf8.h"
#include <algorithm>
#include <cstring>
#include <limits>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
namespace td {
class GetDialogFiltersQuery final : public Td::ResultHandler {
Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> promise_;
public:
explicit GetDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> &&promise)
: promise_(std::move(promise)) {
}
void send() {
send_query(G()->net_query_creator().create(telegram_api::messages_getDialogFilters()));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getDialogFilters>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class UpdateDialogFilterQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit UpdateDialogFilterQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogFilterId dialog_filter_id, tl_object_ptr<telegram_api::DialogFilter> filter) {
int32 flags = 0;
if (filter != nullptr) {
flags |= telegram_api::messages_updateDialogFilter::FILTER_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_updateDialogFilter(flags, dialog_filter_id.get(), std::move(filter))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_updateDialogFilter>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
LOG(INFO) << "Receive result for UpdateDialogFilterQuery: " << result_ptr.ok();
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(ERROR) << "Receive error for UpdateDialogFilterQuery: " << status;
promise_.set_error(std::move(status));
}
};
class UpdateDialogFiltersOrderQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit UpdateDialogFiltersOrderQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(const vector<DialogFilterId> &dialog_filter_ids, int32 main_dialog_list_position) {
auto filter_ids = transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); });
CHECK(0 <= main_dialog_list_position);
CHECK(main_dialog_list_position <= static_cast<int32>(filter_ids.size()));
filter_ids.insert(filter_ids.begin() + main_dialog_list_position, 0);
send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder(std::move(filter_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_updateDialogFiltersOrder>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
LOG(INFO) << "Receive result for UpdateDialogFiltersOrderQuery: " << result_ptr.ok();
promise_.set_value(Unit());
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class GetSuggestedDialogFiltersQuery final : public Td::ResultHandler {
Promise<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> promise_;
public:
explicit GetSuggestedDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> &&promise)
: promise_(std::move(promise)) {
}
void send() {
send_query(G()->net_query_creator().create(telegram_api::messages_getSuggestedDialogFilters()));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getSuggestedDialogFilters>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class GetOnlinesQuery final : public Td::ResultHandler {
DialogId dialog_id_;
public:
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
CHECK(dialog_id.get_type() == DialogType::Channel);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_getOnlines(std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getOnlines>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, result->onlines_, true);
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetOnlinesQuery");
td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, 0, true);
}
};
class GetAllDraftsQuery final : public Td::ResultHandler {
public:
void send() {
send_query(G()->net_query_creator().create(telegram_api::messages_getAllDrafts()));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getAllDrafts>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetAllDraftsQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for GetAllDraftsQuery: " << status;
}
status.ignore();
}
};
class GetDialogQuery final : public Td::ResultHandler {
DialogId dialog_id_;
public:
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
send_query(G()->net_query_creator().create(
telegram_api::messages_getPeerDialogs(
td_->messages_manager_->get_input_dialog_peers({dialog_id}, AccessRights::Read)),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetDialogQuery: " << to_string(result);
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogQuery");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogQuery");
td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
dialog_id = dialog_id_](Result<> result) {
send_closure(actor_id, &MessagesManager::on_get_dialog_query_finished,
dialog_id,
result.is_error() ? result.move_as_error() : Status::OK());
}));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogQuery");
td_->messages_manager_->on_get_dialog_query_finished(dialog_id_, std::move(status));
}
};
class GetDialogsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
bool is_single_ = false;
public:
explicit GetDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(vector<InputDialogId> input_dialog_ids) {
CHECK(!input_dialog_ids.empty());
CHECK(input_dialog_ids.size() <= 100);
is_single_ = input_dialog_ids.size() == 1;
auto input_dialog_peers = InputDialogId::get_input_dialog_peers(input_dialog_ids);
CHECK(input_dialog_peers.size() == input_dialog_ids.size());
send_query(G()->net_query_creator().create(telegram_api::messages_getPeerDialogs(std::move(input_dialog_peers))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetDialogsQuery: " << to_string(result);
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery");
td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_),
std::move(promise_));
}
void on_error(Status status) final {
if (is_single_ && status.code() == 400) {
return promise_.set_value(Unit());
}
promise_.set_error(std::move(status));
}
};
class GetPinnedDialogsQuery final : public Td::ResultHandler {
FolderId folder_id_;
Promise<Unit> promise_;
public:
explicit GetPinnedDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
NetQueryRef send(FolderId folder_id) {
folder_id_ = folder_id;
auto query =
G()->net_query_creator().create(telegram_api::messages_getPinnedDialogs(folder_id.get()), {{folder_id}});
auto result = query.get_weak();
send_query(std::move(query));
return result;
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getPinnedDialogs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive pinned chats in " << folder_id_ << ": " << to_string(result);
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetPinnedDialogsQuery");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetPinnedDialogsQuery");
td_->messages_manager_->on_get_dialogs(folder_id_, std::move(result->dialogs_), -2, std::move(result->messages_),
std::move(promise_));
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class GetDialogUnreadMarksQuery final : public Td::ResultHandler {
public:
void send() {
send_query(G()->net_query_creator().create(telegram_api::messages_getDialogUnreadMarks()));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getDialogUnreadMarks>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto results = result_ptr.move_as_ok();
for (auto &result : results) {
td_->messages_manager_->on_update_dialog_is_marked_as_unread(DialogId(result), true);
}
G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1");
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for GetDialogUnreadMarksQuery: " << status;
}
status.ignore();
}
};
class GetDiscussionMessageQuery final : public Td::ResultHandler {
Promise<MessageThreadInfo> promise_;
DialogId dialog_id_;
MessageId message_id_;
DialogId expected_dialog_id_;
MessageId expected_message_id_;
public:
explicit GetDiscussionMessageQuery(Promise<MessageThreadInfo> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id) {
dialog_id_ = dialog_id;
message_id_ = message_id;
expected_dialog_id_ = expected_dialog_id;
expected_message_id_ = expected_message_id;
CHECK(expected_dialog_id_.is_valid());
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_getDiscussionMessage(std::move(input_peer), message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getDiscussionMessage>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->messages_manager_->process_discussion_message(result_ptr.move_as_ok(), dialog_id_, message_id_,
expected_dialog_id_, expected_message_id_, std::move(promise_));
}
void on_error(Status status) final {
if (expected_dialog_id_ == dialog_id_) {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDiscussionMessageQuery");
}
if (status.message() == "MSG_ID_INVALID") {
td_->messages_manager_->get_message_from_server({dialog_id_, message_id_}, Promise<Unit>(),
"GetDiscussionMessageQuery");
}
promise_.set_error(std::move(status));
}
};
class GetMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit GetMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids) {
send_query(G()->net_query_creator().create(telegram_api::messages_getMessages(std::move(message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetMessagesQuery");
LOG_IF(ERROR, info.is_channel_messages) << "Receive channel messages in GetMessagesQuery";
td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, false,
std::move(promise_), "GetMessagesQuery");
}
void on_error(Status status) final {
if (status.message() == "MESSAGE_IDS_EMPTY") {
promise_.set_value(Unit());
return;
}
promise_.set_error(std::move(status));
}
};
class GetChannelMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
MessageId last_new_message_id_;
bool can_be_inaccessible_ = false;
public:
explicit GetChannelMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, tl_object_ptr<telegram_api::InputChannel> &&input_channel,
vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids, MessageId last_new_message_id) {
channel_id_ = channel_id;
last_new_message_id_ = last_new_message_id;
can_be_inaccessible_ = message_ids.size() == 1 && message_ids[0]->get_id() != telegram_api::inputMessageID::ID;
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::channels_getMessages(std::move(input_channel), std::move(message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, DialogId(channel_id_), result_ptr.move_as_ok(), "GetChannelMessagesQuery");
LOG_IF(ERROR, !info.is_channel_messages) << "Receive ordinary messages in GetChannelMessagesQuery";
// messages with invalid big identifiers can be received as messageEmpty
// bots can receive messageEmpty because of their privacy mode
if (last_new_message_id_.is_valid() && !td_->auth_manager_->is_bot()) {
vector<MessageId> empty_message_ids;
for (auto &message : info.messages) {
if (message->get_id() == telegram_api::messageEmpty::ID) {
auto message_id = MessageId::get_message_id(message, false);
if (message_id.is_valid() && message_id <= last_new_message_id_) {
empty_message_ids.push_back(message_id);
}
}
}
td_->messages_manager_->on_get_empty_messages(DialogId(channel_id_), empty_message_ids);
}
const char *source = can_be_inaccessible_ ? "GetRepliedChannelMessageQuery" : "GetChannelMessagesQuery";
td_->messages_manager_->get_channel_difference_if_needed(
DialogId(channel_id_), std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), source,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_messages, std::move(info.messages),
info.is_channel_messages, false, std::move(promise), source);
}
}));
}
void on_error(Status status) final {
if (status.message() == "MESSAGE_IDS_EMPTY") {
promise_.set_value(Unit());
return;
}
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery");
promise_.set_error(std::move(status));
}
};
class GetScheduledMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit GetScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer, vector<int32> &&message_ids) {
dialog_id_ = dialog_id;
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_getScheduledMessages(std::move(input_peer), std::move(message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getScheduledMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetScheduledMessagesQuery");
LOG_IF(ERROR, info.is_channel_messages != (dialog_id_.get_type() == DialogType::Channel))
<< "Receive wrong messages constructor in GetScheduledMessagesQuery";
td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, true,
std::move(promise_), "GetScheduledMessagesQuery");
}
void on_error(Status status) final {
if (status.message() == "MESSAGE_IDS_EMPTY") {
promise_.set_value(Unit());
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetScheduledMessagesQuery");
promise_.set_error(std::move(status));
}
};
class UpdateDialogPinnedMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit UpdateDialogPinnedMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id, bool is_unpin, bool disable_notification, bool only_for_self) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
LOG(INFO) << "Can't update pinned message in " << dialog_id;
return on_error(Status::Error(400, "Can't update pinned message"));
}
int32 flags = 0;
if (disable_notification) {
flags |= telegram_api::messages_updatePinnedMessage::SILENT_MASK;
}
if (is_unpin) {
flags |= telegram_api::messages_updatePinnedMessage::UNPIN_MASK;
}
if (only_for_self) {
flags |= telegram_api::messages_updatePinnedMessage::PM_ONESIDE_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_updatePinnedMessage(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
std::move(input_peer), message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_updatePinnedMessage>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for UpdateDialogPinnedMessageQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdateDialogPinnedMessageQuery");
promise_.set_error(std::move(status));
}
};
class UnpinAllMessagesQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
DialogId dialog_id_;
public:
explicit UnpinAllMessagesQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId top_thread_message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Write);
if (input_peer == nullptr) {
LOG(INFO) << "Can't unpin all messages in " << dialog_id_;
return on_error(Status::Error(400, "Can't unpin all messages"));
}
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_unpinAllMessages::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_unpinAllMessages(
flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_unpinAllMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UnpinAllMessagesQuery");
promise_.set_error(std::move(status));
}
};
class GetMessageReadParticipantsQuery final : public Td::ResultHandler {
Promise<vector<UserId>> promise_;
DialogId dialog_id_;
public:
explicit GetMessageReadParticipantsQuery(Promise<vector<UserId>> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_getMessageReadParticipants(
std::move(input_peer), message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessageReadParticipants>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(UserId::get_user_ids(result_ptr.ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessageReadParticipantsQuery");
promise_.set_error(std::move(status));
}
};
class TranslateTextQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::text>> promise_;
public:
explicit TranslateTextQuery(Promise<td_api::object_ptr<td_api::text>> &&promise) : promise_(std::move(promise)) {
}
void send(const string &text, const string &from_language_code, const string &to_language_code) {
int flags = telegram_api::messages_translateText::TEXT_MASK;
if (!from_language_code.empty()) {
flags |= telegram_api::messages_translateText::FROM_LANG_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_translateText(flags, nullptr, 0, text, from_language_code, to_language_code)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_translateText>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for TranslateTextQuery: " << to_string(ptr);
switch (ptr->get_id()) {
case telegram_api::messages_translateNoResult::ID:
return promise_.set_value(nullptr);
case telegram_api::messages_translateResultText::ID: {
auto text = telegram_api::move_object_as<telegram_api::messages_translateResultText>(ptr);
return promise_.set_value(td_api::make_object<td_api::text>(text->text_));
}
default:
UNREACHABLE();
}
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class ExportChannelMessageLinkQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
MessageId message_id_;
bool for_group_ = false;
bool ignore_result_ = false;
public:
explicit ExportChannelMessageLinkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, MessageId message_id, bool for_group, bool ignore_result) {
channel_id_ = channel_id;
message_id_ = message_id;
for_group_ = for_group;
ignore_result_ = ignore_result;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
if (for_group) {
flags |= telegram_api::channels_exportMessageLink::GROUPED_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::channels_exportMessageLink(flags, false /*ignored*/, false /*ignored*/, std::move(input_channel),
message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_exportMessageLink>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(DEBUG) << "Receive result for ExportChannelMessageLinkQuery: " << to_string(ptr);
if (!ignore_result_) {
td_->messages_manager_->on_get_public_message_link({DialogId(channel_id_), message_id_}, for_group_,
std::move(ptr->link_), std::move(ptr->html_));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!ignore_result_) {
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ExportChannelMessageLinkQuery");
}
promise_.set_error(std::move(status));
}
};
class GetDialogListQuery final : public Td::ResultHandler {
FolderId folder_id_;
Promise<Unit> promise_;
public:
explicit GetDialogListQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(FolderId folder_id, int32 offset_date, ServerMessageId offset_message_id, DialogId offset_dialog_id,
int32 limit) {
folder_id_ = folder_id;
auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
CHECK(input_peer != nullptr);
int32 flags =
telegram_api::messages_getDialogs::EXCLUDE_PINNED_MASK | telegram_api::messages_getDialogs::FOLDER_ID_MASK;
send_query(G()->net_query_creator().create(
telegram_api::messages_getDialogs(flags, false /*ignored*/, folder_id.get(), offset_date,
offset_message_id.get(), std::move(input_peer), limit, 0),
{{folder_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getDialogs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive chats from chat list of " << folder_id_ << ": " << to_string(ptr);
switch (ptr->get_id()) {
case telegram_api::messages_dialogs::ID: {
auto dialogs = move_tl_object_as<telegram_api::messages_dialogs>(ptr);
td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery");
td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery");
td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_),
narrow_cast<int32>(dialogs->dialogs_.size()),
std::move(dialogs->messages_), std::move(promise_));
break;
}
case telegram_api::messages_dialogsSlice::ID: {
auto dialogs = move_tl_object_as<telegram_api::messages_dialogsSlice>(ptr);
td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery slice");
td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery slice");
td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), max(dialogs->count_, 0),
std::move(dialogs->messages_), std::move(promise_));
break;
}
case telegram_api::messages_dialogsNotModified::ID:
LOG(ERROR) << "Receive " << to_string(ptr);
return on_error(Status::Error(500, "Receive wrong server response messages.dialogsNotModified"));
default:
UNREACHABLE();
}
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class SearchPublicDialogsQuery final : public Td::ResultHandler {
string query_;
public:
void send(const string &query) {
query_ = query;
send_query(G()->net_query_creator().create(telegram_api::contacts_search(query, 3 /* ignored server-side */)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_search>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto dialogs = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SearchPublicDialogsQuery: " << to_string(dialogs);
td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "SearchPublicDialogsQuery");
td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "SearchPublicDialogsQuery");
td_->messages_manager_->on_get_public_dialogs_search_result(query_, std::move(dialogs->my_results_),
std::move(dialogs->results_));
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
if (status.message() == "QUERY_TOO_SHORT") {
return td_->messages_manager_->on_get_public_dialogs_search_result(query_, {}, {});
}
LOG(ERROR) << "Receive error for SearchPublicDialogsQuery: " << status;
}
td_->messages_manager_->on_failed_public_dialogs_search(query_, std::move(status));
}
};
class GetCommonDialogsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
UserId user_id_;
int64 offset_chat_id_ = 0;
public:
explicit GetCommonDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, int64 offset_chat_id, int32 limit) {
user_id_ = user_id;
offset_chat_id_ = offset_chat_id;
send_query(G()->net_query_creator().create(
telegram_api::messages_getCommonChats(std::move(input_user), offset_chat_id, limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getCommonChats>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto chats_ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetCommonDialogsQuery: " << to_string(chats_ptr);
switch (chats_ptr->get_id()) {
case telegram_api::messages_chats::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_),
narrow_cast<int32>(chats->chats_.size()));
break;
}
case telegram_api::messages_chatsSlice::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_),
chats->count_);
break;
}
default:
UNREACHABLE();
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class GetBlockedDialogsQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::messageSenders>> promise_;
int32 offset_;
int32 limit_;
public:
explicit GetBlockedDialogsQuery(Promise<td_api::object_ptr<td_api::messageSenders>> &&promise)
: promise_(std::move(promise)) {
}
void send(int32 offset, int32 limit) {
offset_ = offset;
limit_ = limit;
send_query(G()->net_query_creator().create(telegram_api::contacts_getBlocked(offset, limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_getBlocked>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetBlockedDialogsQuery: " << to_string(ptr);
switch (ptr->get_id()) {
case telegram_api::contacts_blocked::ID: {
auto blocked_peers = move_tl_object_as<telegram_api::contacts_blocked>(ptr);
td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery");
td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery");
td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_,
narrow_cast<int32>(blocked_peers->blocked_.size()),
std::move(blocked_peers->blocked_), std::move(promise_));
break;
}
case telegram_api::contacts_blockedSlice::ID: {
auto blocked_peers = move_tl_object_as<telegram_api::contacts_blockedSlice>(ptr);
td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery slice");
td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery slice");
td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, blocked_peers->count_,
std::move(blocked_peers->blocked_), std::move(promise_));
break;
}
default:
UNREACHABLE();
}
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class CreateChatQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
public:
explicit CreateChatQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(vector<tl_object_ptr<telegram_api::InputUser>> &&input_users, const string &title, MessageTtl message_ttl,
int64 random_id) {
random_id_ = random_id;
int32 flags = telegram_api::messages_createChat::TTL_PERIOD_MASK;
send_query(G()->net_query_creator().create(
telegram_api::messages_createChat(flags, std::move(input_users), title, message_ttl.get_input_ttl_period())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_createChat>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for CreateChatQuery: " << to_string(ptr);
td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Chat,
std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
}
};
class CreateChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
public:
explicit CreateChannelQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(const string &title, bool is_megagroup, const string &about, const DialogLocation &location,
bool for_import, MessageTtl message_ttl, int64 random_id) {
int32 flags = telegram_api::channels_createChannel::TTL_PERIOD_MASK;
if (is_megagroup) {
flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK;
} else {
flags |= telegram_api::channels_createChannel::BROADCAST_MASK;
}
if (!location.empty()) {
flags |= telegram_api::channels_createChannel::GEO_POINT_MASK;
}
if (for_import) {
flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK;
}
random_id_ = random_id;
send_query(G()->net_query_creator().create(telegram_api::channels_createChannel(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, about, location.get_input_geo_point(),
location.get_address(), message_ttl.get_input_ttl_period())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_createChannel>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for CreateChannelQuery: " << to_string(ptr);
td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Channel,
std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
}
};
class CheckHistoryImportQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::MessageFileType>> promise_;
public:
explicit CheckHistoryImportQuery(Promise<tl_object_ptr<td_api::MessageFileType>> &&promise)
: promise_(std::move(promise)) {
}
void send(const string &message_file_head) {
send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImport(message_file_head)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_checkHistoryImport>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for CheckHistoryImportQuery: " << to_string(ptr);
auto file_type = [&]() -> td_api::object_ptr<td_api::MessageFileType> {
if (ptr->pm_) {
return td_api::make_object<td_api::messageFileTypePrivate>(ptr->title_);
} else if (ptr->group_) {
return td_api::make_object<td_api::messageFileTypeGroup>(ptr->title_);
} else {
return td_api::make_object<td_api::messageFileTypeUnknown>();
}
}();
promise_.set_value(std::move(file_type));
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class CheckHistoryImportPeerQuery final : public Td::ResultHandler {
Promise<string> promise_;
DialogId dialog_id_;
public:
explicit CheckHistoryImportPeerQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImportPeer(std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_checkHistoryImportPeer>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for CheckHistoryImportPeerQuery: " << to_string(ptr);
promise_.set_value(std::move(ptr->confirm_text_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "CheckHistoryImportPeerQuery");
promise_.set_error(std::move(status));
}
};
class InitHistoryImportQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
DialogId dialog_id_;
vector<FileId> attached_file_ids_;
public:
explicit InitHistoryImportQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file,
vector<FileId> attached_file_ids) {
CHECK(input_file != nullptr);
file_id_ = file_id;
dialog_id_ = dialog_id;
attached_file_ids_ = std::move(attached_file_ids);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_initHistoryImport(
std::move(input_peer), std::move(input_file), narrow_cast<int32>(attached_file_ids_.size()))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_initHistoryImport>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->file_manager_->delete_partial_remote_location(file_id_);
auto ptr = result_ptr.move_as_ok();
td_->messages_manager_->start_import_messages(dialog_id_, ptr->id_, std::move(attached_file_ids_),
std::move(promise_));
}
void on_error(Status status) final {
if (FileReferenceManager::is_file_reference_error(status)) {
LOG(ERROR) << "Receive file reference error " << status;
}
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
// TODO support FILE_PART_*_MISSING
}
td_->file_manager_->delete_partial_remote_location(file_id_);
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "InitHistoryImportQuery");
promise_.set_error(std::move(status));
}
};
class UploadImportedMediaQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
int64 import_id_;
FileId file_id_;
public:
explicit UploadImportedMediaQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int64 import_id, const string &file_name, FileId file_id,
tl_object_ptr<telegram_api::InputMedia> &&input_media) {
CHECK(input_media != nullptr);
dialog_id_ = dialog_id;
import_id_ = import_id;
file_id_ = file_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_uploadImportedMedia(
std::move(input_peer), import_id, file_name, std::move(input_media))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_uploadImportedMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->file_manager_->delete_partial_remote_location(file_id_);
// ignore response
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (FileReferenceManager::is_file_reference_error(status)) {
LOG(ERROR) << "Receive file reference error " << status;
}
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
// TODO support FILE_PART_*_MISSING
}
td_->file_manager_->delete_partial_remote_location(file_id_);
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadImportedMediaQuery");
promise_.set_error(std::move(status));
}
};
class StartImportHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit StartImportHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int64 import_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(
G()->net_query_creator().create(telegram_api::messages_startHistoryImport(std::move(input_peer), import_id)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_startHistoryImport>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
if (!result_ptr.ok()) {
return on_error(Status::Error(500, "Import history returned false"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartImportHistoryQuery");
promise_.set_error(std::move(status));
}
};
class EditDialogPhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
bool was_uploaded_ = false;
string file_reference_;
DialogId dialog_id_;
public:
explicit EditDialogPhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo) {
CHECK(input_chat_photo != nullptr);
file_id_ = file_id;
was_uploaded_ = FileManager::extract_was_uploaded(input_chat_photo);
file_reference_ = FileManager::extract_file_reference(input_chat_photo);
dialog_id_ = dialog_id;
switch (dialog_id.get_type()) {
case DialogType::Chat:
send_query(G()->net_query_creator().create(
telegram_api::messages_editChatPhoto(dialog_id.get_chat_id().get(), std::move(input_chat_photo))));
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::channels_editPhoto(std::move(input_channel), std::move(input_chat_photo))));
break;
}
default:
UNREACHABLE();
}
}
void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_editChatPhoto::ReturnType,
telegram_api::channels_editPhoto::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_editChatPhoto>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for EditDialogPhotoQuery: " << to_string(ptr);
if (file_id_.is_valid() && was_uploaded_) {
td_->file_manager_->delete_partial_remote_location(file_id_);
}
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (file_id_.is_valid() && was_uploaded_) {
td_->file_manager_->delete_partial_remote_location(file_id_);
}
if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
if (file_id_.is_valid() && !was_uploaded_) {
VLOG(file_references) << "Receive " << status << " for " << file_id_;
td_->file_manager_->delete_file_reference(file_id_, file_reference_);
td_->messages_manager_->upload_dialog_photo(dialog_id_, file_id_, false, 0.0, false, std::move(promise_), {-1});
return;
} else {
LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_
<< ", was_uploaded = " << was_uploaded_;
}
}
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogPhotoQuery");
}
td_->updates_manager_->get_difference("EditDialogPhotoQuery");
promise_.set_error(std::move(status));
}
};
class EditDialogTitleQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit EditDialogTitleQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const string &title) {
dialog_id_ = dialog_id;
switch (dialog_id.get_type()) {
case DialogType::Chat:
send_query(G()->net_query_creator().create(
telegram_api::messages_editChatTitle(dialog_id.get_chat_id().get(), title)));
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(telegram_api::channels_editTitle(std::move(input_channel), title)));
break;
}
default:
UNREACHABLE();
}
}
void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_editChatTitle::ReturnType,
telegram_api::channels_editTitle::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_editChatTitle>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for EditDialogTitleQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
td_->updates_manager_->get_difference("EditDialogTitleQuery");
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogTitleQuery");
}
promise_.set_error(std::move(status));
}
};
class SetChatAvailableReactionsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit SetChatAvailableReactionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const ChatReactions &available_reactions) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_setChatAvailableReactions(
std::move(input_peer), available_reactions.get_input_chat_reactions())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setChatAvailableReactions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SetChatAvailableReactionsQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatAvailableReactionsQuery");
td_->messages_manager_->reload_dialog_info_full(dialog_id_, "SetChatAvailableReactionsQuery");
}
promise_.set_error(std::move(status));
}
};
class SetChatThemeQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit SetChatThemeQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const string &theme_name) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_setChatTheme(std::move(input_peer), theme_name)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setChatTheme>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SetChatThemeQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatThemeQuery");
}
promise_.set_error(std::move(status));
}
};
class SetHistoryTtlQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit SetHistoryTtlQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int32 period) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_setHistoryTTL(std::move(input_peer), period)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setHistoryTTL>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SetHistoryTtlQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetHistoryTtlQuery");
}
promise_.set_error(std::move(status));
}
};
class EditChatDefaultBannedRightsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit EditChatDefaultBannedRightsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, RestrictedRights permissions) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_editChatDefaultBannedRights(
std::move(input_peer), permissions.get_chat_banned_rights())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_editChatDefaultBannedRights>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for EditChatDefaultBannedRightsQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatDefaultBannedRightsQuery");
}
promise_.set_error(std::move(status));
}
};
class ToggleNoForwardsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit ToggleNoForwardsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, bool has_protected_content) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_toggleNoForwards(std::move(input_peer), has_protected_content)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_toggleNoForwards>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for ToggleNoForwardsQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
promise_.set_value(Unit());
return;
} else {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleNoForwardsQuery");
}
promise_.set_error(std::move(status));
}
};
class SaveDraftMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit SaveDraftMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const unique_ptr<DraftMessage> &draft_message) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
LOG(INFO) << "Can't update draft message because have no write access to " << dialog_id;
return on_error(Status::Error(400, "Can't save draft message"));
}
int32 flags = 0;
ServerMessageId reply_to_message_id;
if (draft_message != nullptr) {
if (draft_message->reply_to_message_id.is_valid() && draft_message->reply_to_message_id.is_server()) {
reply_to_message_id = draft_message->reply_to_message_id.get_server_message_id();
flags |= MessagesManager::SEND_MESSAGE_FLAG_IS_REPLY;
}
if (draft_message->input_message_text.disable_web_page_preview) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
if (!draft_message->input_message_text.text.entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
}
vector<tl_object_ptr<telegram_api::MessageEntity>> input_message_entities;
if (draft_message != nullptr) {
input_message_entities = get_input_message_entities(
td_->contacts_manager_.get(), draft_message->input_message_text.text.entities, "SaveDraftMessageQuery");
}
send_query(G()->net_query_creator().create(
telegram_api::messages_saveDraft(flags, false /*ignored*/, reply_to_message_id.get(), 0, std::move(input_peer),
draft_message == nullptr ? "" : draft_message->input_message_text.text.text,
std::move(input_message_entities)),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_saveDraft>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
on_error(Status::Error(400, "Save draft failed"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDraftMessageQuery")) {
LOG(ERROR) << "Receive error for SaveDraftMessageQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
class ClearAllDraftsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit ClearAllDraftsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send() {
send_query(G()->net_query_creator().create(telegram_api::messages_clearAllDrafts()));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_clearAllDrafts>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
LOG(INFO) << "Receive result for ClearAllDraftsQuery: " << result_ptr.ok();
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for ClearAllDraftsQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
class ToggleDialogPinQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
bool is_pinned_;
public:
explicit ToggleDialogPinQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, bool is_pinned) {
dialog_id_ = dialog_id;
is_pinned_ = is_pinned;
auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
if (is_pinned) {
flags |= telegram_api::messages_toggleDialogPin::PINNED_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_toggleDialogPin(flags, false /*ignored*/, std::move(input_peer)), {{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_toggleDialogPin>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
on_error(Status::Error(400, "Toggle dialog pin failed"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogPinQuery")) {
LOG(ERROR) << "Receive error for ToggleDialogPinQuery: " << status;
}
td_->messages_manager_->on_update_pinned_dialogs(FolderId::main());
td_->messages_manager_->on_update_pinned_dialogs(FolderId::archive());
promise_.set_error(std::move(status));
}
};
class ReorderPinnedDialogsQuery final : public Td::ResultHandler {
FolderId folder_id_;
Promise<Unit> promise_;
public:
explicit ReorderPinnedDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(FolderId folder_id, const vector<DialogId> &dialog_ids) {
folder_id_ = folder_id;
int32 flags = telegram_api::messages_reorderPinnedDialogs::FORCE_MASK;
send_query(G()->net_query_creator().create(telegram_api::messages_reorderPinnedDialogs(
flags, true /*ignored*/, folder_id.get(),
td_->messages_manager_->get_input_dialog_peers(dialog_ids, AccessRights::Read))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_reorderPinnedDialogs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
if (!result) {
return on_error(Status::Error(400, "Result is false"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for ReorderPinnedDialogsQuery: " << status;
}
td_->messages_manager_->on_update_pinned_dialogs(folder_id_);
promise_.set_error(std::move(status));
}
};
class ToggleDialogUnreadMarkQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
bool is_marked_as_unread_;
public:
explicit ToggleDialogUnreadMarkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, bool is_marked_as_unread) {
dialog_id_ = dialog_id;
is_marked_as_unread_ = is_marked_as_unread;
auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
if (is_marked_as_unread) {
flags |= telegram_api::messages_markDialogUnread::UNREAD_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_markDialogUnread(flags, false /*ignored*/, std::move(input_peer)), {{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_markDialogUnread>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
on_error(Status::Error(400, "Toggle dialog mark failed"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogUnreadMarkQuery")) {
LOG(ERROR) << "Receive error for ToggleDialogUnreadMarkQuery: " << status;
}
if (!G()->close_flag()) {
td_->messages_manager_->on_update_dialog_is_marked_as_unread(dialog_id_, !is_marked_as_unread_);
}
promise_.set_error(std::move(status));
}
};
class ToggleDialogIsBlockedQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
bool is_blocked_;
public:
explicit ToggleDialogIsBlockedQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, bool is_blocked) {
dialog_id_ = dialog_id;
is_blocked_ = is_blocked;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Know);
CHECK(input_peer != nullptr && input_peer->get_id() != telegram_api::inputPeerEmpty::ID);
vector<ChainId> chain_ids{{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}};
auto query = is_blocked ? G()->net_query_creator().create(telegram_api::contacts_block(std::move(input_peer)),
std::move(chain_ids))
: G()->net_query_creator().create(telegram_api::contacts_unblock(std::move(input_peer)),
std::move(chain_ids));
send_query(std::move(query));
}
void on_result(BufferSlice packet) final {
static_assert(
std::is_same<telegram_api::contacts_block::ReturnType, telegram_api::contacts_unblock::ReturnType>::value, "");
auto result_ptr = fetch_result<telegram_api::contacts_block>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
LOG_IF(WARNING, !result) << "Block/Unblock " << dialog_id_ << " has failed";
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogIsBlockedQuery")) {
LOG(ERROR) << "Receive error for ToggleDialogIsBlockedQuery: " << status;
}
if (!G()->close_flag()) {
td_->messages_manager_->on_update_dialog_is_blocked(dialog_id_, !is_blocked_);
td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "ToggleDialogIsBlockedQuery");
td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ToggleDialogIsBlockedQuery");
}
promise_.set_error(std::move(status));
}
};
class GetMessagesViewsQuery final : public Td::ResultHandler {
DialogId dialog_id_;
vector<MessageId> message_ids_;
public:
void send(DialogId dialog_id, vector<MessageId> &&message_ids, bool increment_view_counter) {
dialog_id_ = dialog_id;
message_ids_ = std::move(message_ids);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_getMessagesViews(
std::move(input_peer), MessageId::get_server_message_ids(message_ids_), increment_view_counter)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessagesViews>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
auto interaction_infos = std::move(result->views_);
if (message_ids_.size() != interaction_infos.size()) {
return on_error(Status::Error(500, "Wrong number of message views returned"));
}
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetMessagesViewsQuery");
for (size_t i = 0; i < message_ids_.size(); i++) {
FullMessageId full_message_id{dialog_id_, message_ids_[i]};
auto *info = interaction_infos[i].get();
auto flags = info->flags_;
auto view_count = (flags & telegram_api::messageViews::VIEWS_MASK) != 0 ? info->views_ : 0;
auto forward_count = (flags & telegram_api::messageViews::FORWARDS_MASK) != 0 ? info->forwards_ : 0;
td_->messages_manager_->on_update_message_interaction_info(full_message_id, view_count, forward_count, true,
std::move(info->replies_));
}
td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_);
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesViewsQuery")) {
LOG(ERROR) << "Receive error for GetMessagesViewsQuery: " << status;
}
td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_);
}
};
class GetExtendedMediaQuery final : public Td::ResultHandler {
DialogId dialog_id_;
vector<MessageId> message_ids_;
public:
void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
dialog_id_ = dialog_id;
message_ids_ = std::move(message_ids);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_getExtendedMedia(
std::move(input_peer), MessageId::get_server_message_ids(message_ids_))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getExtendedMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetExtendedMediaQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
td_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_);
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExtendedMediaQuery");
td_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_);
}
};
class ReadMessagesContentsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit ReadMessagesContentsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(vector<MessageId> &&message_ids) {
send_query(G()->net_query_creator().create(
telegram_api::messages_readMessageContents(MessageId::get_server_message_ids(message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readMessageContents>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
LOG(INFO) << "Receive result for ReadMessagesContentsQuery: " << to_string(affected_messages);
if (affected_messages->pts_count_ > 0) {
td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
affected_messages->pts_count_, Time::now(), Promise<Unit>(),
"read messages content query");
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for read message contents: " << status;
}
promise_.set_error(std::move(status));
}
};
class ReadChannelMessagesContentsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
public:
explicit ReadChannelMessagesContentsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, vector<MessageId> &&message_ids) {
channel_id_ = channel_id;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
LOG(ERROR) << "Have no input channel for " << channel_id;
return on_error(Status::Error(500, "Can't read channel message contents"));
}
send_query(G()->net_query_creator().create(telegram_api::channels_readMessageContents(
std::move(input_channel), MessageId::get_server_message_ids(message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_readMessageContents>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
LOG_IF(ERROR, !result) << "Read channel messages contents failed";
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) {
LOG(ERROR) << "Receive error for read messages contents in " << channel_id_ << ": " << status;
}
promise_.set_error(std::move(status));
}
};
class GetDialogMessageByDateQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
int32 date_;
int64 random_id_;
public:
explicit GetDialogMessageByDateQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int32 date, int64 random_id) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
date_ = date;
random_id_ = random_id;
send_query(G()->net_query_creator().create(
telegram_api::messages_getHistory(std::move(input_peer), 0, date, -3, 5, 0, 0, 0)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetDialogMessageByDateQuery");
td_->messages_manager_->get_channel_difference_if_needed(
dialog_id_, std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, date = date_,
random_id = random_id_,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_success, dialog_id, date, random_id,
std::move(info.messages), std::move(promise));
}
}));
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogMessageByDateQuery")) {
LOG(ERROR) << "Receive error for GetDialogMessageByDateQuery in " << dialog_id_ << ": " << status;
}
promise_.set_error(std::move(status));
td_->messages_manager_->on_get_dialog_message_by_date_fail(random_id_);
}
};
class GetHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
MessageId from_message_id_;
MessageId old_last_new_message_id_;
int32 offset_;
int32 limit_;
bool from_the_end_;
public:
explicit GetHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id, int32 offset,
int32 limit) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
CHECK(offset < 0);
dialog_id_ = dialog_id;
from_message_id_ = from_message_id;
old_last_new_message_id_ = old_last_new_message_id;
offset_ = offset;
limit_ = limit;
from_the_end_ = false;
send_query(G()->net_query_creator().create(telegram_api::messages_getHistory(
std::move(input_peer), from_message_id.get_server_message_id().get(), 0, offset, limit, 0, 0, 0)));
}
void send_get_from_the_end(DialogId dialog_id, MessageId old_last_new_message_id, int32 limit) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
old_last_new_message_id_ = old_last_new_message_id;
offset_ = 0;
limit_ = limit;
from_the_end_ = true;
send_query(G()->net_query_creator().create(
telegram_api::messages_getHistory(std::move(input_peer), 0, 0, 0, limit, 0, 0, 0)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetHistoryQuery");
td_->messages_manager_->get_channel_difference_if_needed(
dialog_id_, std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
from_message_id = from_message_id_, old_last_new_message_id = old_last_new_message_id_,
offset = offset_, limit = limit_, from_the_end = from_the_end_,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
// TODO use info.total_count, info.pts
send_closure(actor_id, &MessagesManager::on_get_history, dialog_id, from_message_id,
old_last_new_message_id, offset, limit, from_the_end, std::move(info.messages),
std::move(promise));
}
}));
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetHistoryQuery")) {
LOG(ERROR) << "Receive error for GetHistoryQuery in " << dialog_id_ << ": " << status;
}
promise_.set_error(std::move(status));
}
};
class ReadHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit ReadHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId max_message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_readHistory(std::move(input_peer), max_message_id.get_server_message_id().get()),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
LOG(INFO) << "Receive result for ReadHistoryQuery: " << to_string(affected_messages);
if (affected_messages->pts_count_ > 0) {
td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
affected_messages->pts_count_, Time::now(), Promise<Unit>(),
"read history query");
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadHistoryQuery")) {
LOG(ERROR) << "Receive error for ReadHistoryQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
class ReadChannelHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
public:
explicit ReadChannelHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, MessageId max_message_id) {
channel_id_ = channel_id;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::channels_readHistory(std::move(input_channel), max_message_id.get_server_message_id().get()),
{{DialogId(channel_id)}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_readHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) {
LOG(ERROR) << "Receive error for ReadChannelHistoryQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
class ReadDiscussionQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit ReadDiscussionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId top_thread_message_id, MessageId max_message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_readDiscussion(std::move(input_peer),
top_thread_message_id.get_server_message_id().get(),
max_message_id.get_server_message_id().get()),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readDiscussion>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadDiscussionQuery");
promise_.set_error(std::move(status));
}
};
class GetSearchResultCalendarQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
MessageId from_message_id_;
MessageSearchFilter filter_;
int64 random_id_;
public:
explicit GetSearchResultCalendarQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
dialog_id_ = dialog_id;
from_message_id_ = from_message_id;
filter_ = filter;
random_id_ = random_id;
send_query(G()->net_query_creator().create(telegram_api::messages_getSearchResultsCalendar(
std::move(input_peer), get_input_messages_filter(filter), from_message_id.get_server_message_id().get(), 0)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getSearchResultsCalendar>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetSearchResultCalendarQuery: " << to_string(result);
td_->contacts_manager_->on_get_users(std::move(result->users_), "GetSearchResultCalendarQuery");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetSearchResultCalendarQuery");
// unused: inexact:flags.0?true min_date:int min_msg_id:int offset_id_offset:flags.1?int
MessagesInfo info;
info.messages = std::move(result->messages_);
info.total_count = result->count_;
info.is_channel_messages = dialog_id_.get_type() == DialogType::Channel;
td_->messages_manager_->get_channel_difference_if_needed(
dialog_id_, std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
from_message_id = from_message_id_, filter = filter_, random_id = random_id_,
periods = std::move(result->periods_),
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_message_search_result_calendar, dialog_id, from_message_id,
filter, random_id, info.total_count, std::move(info.messages), std::move(periods),
std::move(promise));
}
}));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultCalendarQuery");
td_->messages_manager_->on_failed_get_message_search_result_calendar(dialog_id_, random_id_);
promise_.set_error(std::move(status));
}
};
class GetMessagePositionQuery final : public Td::ResultHandler {
Promise<int32> promise_;
DialogId dialog_id_;
MessageId message_id_;
MessageId top_thread_message_id_;
MessageSearchFilter filter_;
public:
explicit GetMessagePositionQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id, MessageSearchFilter filter, MessageId top_thread_message_id) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
dialog_id_ = dialog_id;
message_id_ = message_id;
top_thread_message_id_ = top_thread_message_id;
filter_ = filter;
if (filter == MessageSearchFilter::Empty && !top_thread_message_id.is_valid()) {
send_query(G()->net_query_creator().create(telegram_api::messages_getHistory(
std::move(input_peer), message_id.get_server_message_id().get(), 0, -1, 1, 0, 0, 0)));
} else {
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_search::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_search(
flags, std::move(input_peer), string(), nullptr, top_thread_message_id.get_server_message_id().get(),
get_input_messages_filter(filter), 0, std::numeric_limits<int32>::max(),
message_id.get_server_message_id().get(), -1, 1, std::numeric_limits<int32>::max(), 0, 0)));
}
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_search>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto messages_ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetMessagePositionQuery: " << to_string(messages_ptr);
switch (messages_ptr->get_id()) {
case telegram_api::messages_messages::ID: {
auto messages = move_tl_object_as<telegram_api::messages_messages>(messages_ptr);
if (messages->messages_.size() != 1 ||
MessageId::get_message_id(messages->messages_[0], false) != message_id_) {
return promise_.set_error(Status::Error(400, "Message not found by the filter"));
}
return promise_.set_value(narrow_cast<int32>(messages->messages_.size()));
}
case telegram_api::messages_messagesSlice::ID: {
auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(messages_ptr);
if (messages->messages_.size() != 1 ||
MessageId::get_message_id(messages->messages_[0], false) != message_id_) {
return promise_.set_error(Status::Error(400, "Message not found by the filter"));
}
if (messages->offset_id_offset_ <= 0) {
LOG(ERROR) << "Failed to receive position for " << message_id_ << " in thread of " << top_thread_message_id_
<< " in " << dialog_id_ << " by " << filter_;
return promise_.set_error(Status::Error(400, "Message position is unknown"));
}
return promise_.set_value(std::move(messages->offset_id_offset_));
}
case telegram_api::messages_channelMessages::ID: {
auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(messages_ptr);
if (messages->messages_.size() != 1 ||
MessageId::get_message_id(messages->messages_[0], false) != message_id_) {
return promise_.set_error(Status::Error(400, "Message not found by the filter"));
}
if (messages->offset_id_offset_ <= 0) {
LOG(ERROR) << "Failed to receive position for " << message_id_ << " in " << dialog_id_ << " by " << filter_;
return promise_.set_error(Status::Error(500, "Message position is unknown"));
}
return promise_.set_value(std::move(messages->offset_id_offset_));
}
case telegram_api::messages_messagesNotModified::ID:
LOG(ERROR) << "Server returned messagesNotModified in response to GetMessagePositionQuery";
return promise_.set_error(Status::Error(500, "Receive invalid response"));
default:
UNREACHABLE();
break;
}
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePositionQuery");
promise_.set_error(std::move(status));
}
};
class SearchMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
string query_;
DialogId sender_dialog_id_;
MessageId from_message_id_;
int32 offset_;
int32 limit_;
MessageSearchFilter filter_;
MessageId top_thread_message_id_;
int64 random_id_;
bool handle_errors_ = true;
public:
explicit SearchMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset,
int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id) {
auto input_peer = dialog_id.is_valid() ? td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read)
: make_tl_object<telegram_api::inputPeerEmpty>();
CHECK(input_peer != nullptr);
dialog_id_ = dialog_id;
query_ = query;
sender_dialog_id_ = sender_dialog_id;
from_message_id_ = from_message_id;
offset_ = offset;
limit_ = limit;
filter_ = filter;
top_thread_message_id_ = top_thread_message_id;
random_id_ = random_id;
auto top_msg_id = top_thread_message_id.get_server_message_id().get();
auto offset_id = from_message_id.get_server_message_id().get();
if (filter == MessageSearchFilter::UnreadMention) {
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_getUnreadMentions::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadMentions(
flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits<int32>::max(), 0)));
} else if (filter == MessageSearchFilter::UnreadReaction) {
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_getUnreadReactions::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadReactions(
flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits<int32>::max(), 0)));
} else if (top_thread_message_id.is_valid() && query.empty() && !sender_dialog_id.is_valid() &&
filter == MessageSearchFilter::Empty) {
handle_errors_ = dialog_id.get_type() != DialogType::Channel ||
!td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id());
send_query(G()->net_query_creator().create(telegram_api::messages_getReplies(
std::move(input_peer), top_msg_id, offset_id, 0, offset, limit, std::numeric_limits<int32>::max(), 0, 0)));
} else {
int32 flags = 0;
tl_object_ptr<telegram_api::InputPeer> sender_input_peer;
if (sender_dialog_id.is_valid()) {
flags |= telegram_api::messages_search::FROM_ID_MASK;
sender_input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know);
CHECK(sender_input_peer != nullptr);
}
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_search::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_search(flags, std::move(input_peer), query, std::move(sender_input_peer), top_msg_id,
get_input_messages_filter(filter), 0, std::numeric_limits<int32>::max(),
offset_id, offset, limit, std::numeric_limits<int32>::max(), 0, 0)));
}
}
void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_getUnreadMentions::ReturnType,
telegram_api::messages_search::ReturnType>::value,
"");
static_assert(std::is_same<telegram_api::messages_getUnreadReactions::ReturnType,
telegram_api::messages_search::ReturnType>::value,
"");
static_assert(
std::is_same<telegram_api::messages_getReplies::ReturnType, telegram_api::messages_search::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_search>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "SearchMessagesQuery");
td_->messages_manager_->get_channel_difference_if_needed(
dialog_id_, std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
query = std::move(query_), sender_dialog_id = sender_dialog_id_,
from_message_id = from_message_id_, offset = offset_, limit = limit_, filter = filter_,
top_thread_message_id = top_thread_message_id_, random_id = random_id_,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_dialog_messages_search_result, dialog_id, query,
sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id, random_id,
info.total_count, std::move(info.messages), std::move(promise));
}
}));
}
void on_error(Status status) final {
if (handle_errors_) {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SearchMessagesQuery");
}
td_->messages_manager_->on_failed_dialog_messages_search(dialog_id_, random_id_);
promise_.set_error(std::move(status));
}
};
class GetSearchResultPositionsQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::messagePositions>> promise_;
DialogId dialog_id_;
MessageSearchFilter filter_;
public:
explicit GetSearchResultPositionsQuery(Promise<td_api::object_ptr<td_api::messagePositions>> &&promise)
: promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
filter_ = filter;
send_query(G()->net_query_creator().create(
telegram_api::messages_getSearchResultsPositions(std::move(input_peer), get_input_messages_filter(filter),
from_message_id.get_server_message_id().get(), limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getSearchResultsPositions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->messages_manager_->on_get_dialog_sparse_message_positions(dialog_id_, filter_, result_ptr.move_as_ok(),
std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultPositionsQuery");
promise_.set_error(std::move(status));
}
};
class GetSearchCountersQuery final : public Td::ResultHandler {
Promise<int32> promise_;
DialogId dialog_id_;
MessageSearchFilter filter_;
public:
explicit GetSearchCountersQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageSearchFilter filter) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
filter_ = filter;
CHECK(filter != MessageSearchFilter::Empty);
CHECK(filter != MessageSearchFilter::UnreadMention);
CHECK(filter != MessageSearchFilter::FailedToSend);
CHECK(filter != MessageSearchFilter::UnreadReaction);
vector<telegram_api::object_ptr<telegram_api::MessagesFilter>> filters;
filters.push_back(get_input_messages_filter(filter));
int32 flags = 0;
send_query(G()->net_query_creator().create(
telegram_api::messages_getSearchCounters(flags, std::move(input_peer), 0, std::move(filters))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getSearchCounters>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
if (result.size() != 1 || result[0]->filter_->get_id() != get_input_messages_filter(filter_)->get_id()) {
LOG(ERROR) << "Receive unexpected response for get message count in " << dialog_id_ << " with filter " << filter_
<< ": " << to_string(result);
return on_error(Status::Error(500, "Receive wrong response"));
}
td_->messages_manager_->on_get_dialog_message_count(dialog_id_, filter_, result[0]->count_, std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchCountersQuery");
promise_.set_error(std::move(status));
}
};
class SearchMessagesGlobalQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
string query_;
int32 offset_date_;
DialogId offset_dialog_id_;
MessageId offset_message_id_;
int32 limit_;
MessageSearchFilter filter_;
int32 min_date_;
int32 max_date_;
int64 random_id_;
public:
explicit SearchMessagesGlobalQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date,
DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter,
int32 min_date, int32 max_date, int64 random_id) {
query_ = query;
offset_date_ = offset_date;
offset_dialog_id_ = offset_dialog_id;
offset_message_id_ = offset_message_id;
limit_ = limit;
random_id_ = random_id;
filter_ = filter;
min_date_ = min_date;
max_date_ = max_date;
auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
CHECK(input_peer != nullptr);
int32 flags = 0;
if (!ignore_folder_id) {
flags |= telegram_api::messages_searchGlobal::FOLDER_ID_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal(
flags, folder_id.get(), query, get_input_messages_filter(filter), min_date_, max_date_, offset_date_,
std::move(input_peer), offset_message_id.get_server_message_id().get(), limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_searchGlobal>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchMessagesGlobalQuery");
td_->messages_manager_->get_channel_differences_if_needed(
std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), query = std::move(query_),
offset_date = offset_date_, offset_dialog_id = offset_dialog_id_,
offset_message_id = offset_message_id_, limit = limit_, filter = std::move(filter_),
min_date = min_date_, max_date = max_date_, random_id = random_id_,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_messages_search_result, query, offset_date,
offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, random_id,
info.total_count, std::move(info.messages), info.next_rate, std::move(promise));
}
}));
}
void on_error(Status status) final {
td_->messages_manager_->on_failed_messages_search(random_id_);
promise_.set_error(std::move(status));
}
};
class GetAllScheduledMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
uint32 generation_;
public:
explicit GetAllScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int64 hash, uint32 generation) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
dialog_id_ = dialog_id;
generation_ = generation;
send_query(
G()->net_query_creator().create(telegram_api::messages_getScheduledHistory(std::move(input_peer), hash)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getScheduledHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
if (result_ptr.ok()->get_id() == telegram_api::messages_messagesNotModified::ID) {
td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, Auto(), true);
} else {
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetAllScheduledMessagesQuery");
td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, std::move(info.messages),
false);
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetAllScheduledMessagesQuery");
promise_.set_error(std::move(status));
}
};
class SearchSentMediaQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::foundMessages>> promise_;
public:
explicit SearchSentMediaQuery(Promise<td_api::object_ptr<td_api::foundMessages>> &&promise)
: promise_(std::move(promise)) {
}
void send(const string &query, int32 limit) {
send_query(G()->net_query_creator().create(telegram_api::messages_searchSentMedia(
query, telegram_api::make_object<telegram_api::inputMessagesFilterDocument>(), limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_searchSentMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchSentMediaQuery");
td_->messages_manager_->get_channel_differences_if_needed(
std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_outgoing_document_messages, std::move(info.messages),
std::move(promise));
}
}));
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class GetRecentLocationsQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::messages>> promise_;
DialogId dialog_id_;
int32 limit_;
public:
explicit GetRecentLocationsQuery(Promise<td_api::object_ptr<td_api::messages>> &&promise)
: promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int32 limit) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Chat is not accessible"));
}
dialog_id_ = dialog_id;
limit_ = limit;
send_query(
G()->net_query_creator().create(telegram_api::messages_getRecentLocations(std::move(input_peer), limit, 0)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getRecentLocations>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, dialog_id_, result_ptr.move_as_ok(), "GetRecentLocationsQuery");
td_->messages_manager_->get_channel_difference_if_needed(
dialog_id_, std::move(info),
PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, limit = limit_,
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_recent_locations, dialog_id, limit, info.total_count,
std::move(info.messages), std::move(promise));
}
}));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetRecentLocationsQuery");
promise_.set_error(std::move(status));
}
};
class GetMessagePublicForwardsQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::foundMessages>> promise_;
DialogId dialog_id_;
int32 limit_;
public:
explicit GetMessagePublicForwardsQuery(Promise<td_api::object_ptr<td_api::foundMessages>> &&promise)
: promise_(std::move(promise)) {
}
void send(DcId dc_id, FullMessageId full_message_id, int32 offset_date, DialogId offset_dialog_id,
ServerMessageId offset_message_id, int32 limit) {
dialog_id_ = full_message_id.get_dialog_id();
limit_ = limit;
auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
CHECK(input_peer != nullptr);
send_query(
G()->net_query_creator().create(telegram_api::stats_getMessagePublicForwards(
td_->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()),
full_message_id.get_message_id().get_server_message_id().get(), offset_date,
std::move(input_peer), offset_message_id.get(), limit),
{}, dc_id));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::stats_getMessagePublicForwards>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetMessagePublicForwardsQuery");
td_->messages_manager_->get_channel_differences_if_needed(
std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
promise = std::move(promise_)](Result<MessagesInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
auto info = result.move_as_ok();
send_closure(actor_id, &MessagesManager::on_get_message_public_forwards, info.total_count,
std::move(info.messages), info.next_rate, std::move(promise));
}
}));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePublicForwardsQuery");
promise_.set_error(std::move(status));
}
};
class HidePromoDataQuery final : public Td::ResultHandler {
DialogId dialog_id_;
public:
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::help_hidePromoData(std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_hidePromoData>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
// we are not interested in the result
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "HidePromoDataQuery")) {
LOG(ERROR) << "Receive error for sponsored chat hiding: " << status;
}
}
};
class DeleteHistoryQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
DialogId dialog_id_;
public:
explicit DeleteHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
int32 flags = 0;
if (!remove_from_dialog_list) {
flags |= telegram_api::messages_deleteHistory::JUST_CLEAR_MASK;
}
if (revoke) {
flags |= telegram_api::messages_deleteHistory::REVOKE_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_deleteHistory(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
max_message_id.get_server_message_id().get(), 0, 0)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteHistoryQuery");
promise_.set_error(std::move(status));
}
};
class DeleteTopicHistoryQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
ChannelId channel_id_;
public:
explicit DeleteTopicHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId top_thread_message_id) {
CHECK(dialog_id.get_type() == DialogType::Channel);
channel_id_ = dialog_id.get_channel_id();
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id_);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(telegram_api::channels_deleteTopicHistory(
std::move(input_channel), top_thread_message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteTopicHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteTopicHistoryQuery");
promise_.set_error(std::move(status));
}
};
class DeleteChannelHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
MessageId max_message_id_;
bool allow_error_;
public:
explicit DeleteChannelHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, MessageId max_message_id, bool allow_error, bool revoke) {
channel_id_ = channel_id;
max_message_id_ = max_message_id;
allow_error_ = allow_error;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
int32 flags = 0;
if (revoke) {
flags |= telegram_api::channels_deleteHistory::FOR_EVERYONE_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::channels_deleteHistory(
flags, false /*ignored*/, std::move(input_channel), max_message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for DeleteChannelHistoryQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) {
LOG(ERROR) << "Receive error for DeleteChannelHistoryQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
class DeleteMessagesByDateQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
DialogId dialog_id_;
public:
explicit DeleteMessagesByDateQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
int32 flags = telegram_api::messages_deleteHistory::JUST_CLEAR_MASK |
telegram_api::messages_deleteHistory::MIN_DATE_MASK |
telegram_api::messages_deleteHistory::MAX_DATE_MASK;
if (revoke) {
flags |= telegram_api::messages_deleteHistory::REVOKE_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_deleteHistory(
flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), 0, min_date, max_date)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteMessagesByDateQuery");
promise_.set_error(std::move(status));
}
};
class DeletePhoneCallHistoryQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
public:
explicit DeletePhoneCallHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(bool revoke) {
int32 flags = 0;
if (revoke) {
flags |= telegram_api::messages_deletePhoneCallHistory::REVOKE_MASK;
}
send_query(
G()->net_query_creator().create(telegram_api::messages_deletePhoneCallHistory(flags, false /*ignored*/)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deletePhoneCallHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
if (!affected_messages->messages_.empty()) {
td_->messages_manager_->process_pts_update(
make_tl_object<telegram_api::updateDeleteMessages>(std::move(affected_messages->messages_), 0, 0));
}
promise_.set_value(AffectedHistory(std::move(affected_messages)));
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class BlockFromRepliesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit BlockFromRepliesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam) {
int32 flags = 0;
if (need_delete_message) {
flags |= telegram_api::contacts_blockFromReplies::DELETE_MESSAGE_MASK;
}
if (need_delete_all_messages) {
flags |= telegram_api::contacts_blockFromReplies::DELETE_HISTORY_MASK;
}
if (report_spam) {
flags |= telegram_api::contacts_blockFromReplies::REPORT_SPAM_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::contacts_blockFromReplies(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, message_id.get_server_message_id().get())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_blockFromReplies>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for BlockFromRepliesQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class DeleteParticipantHistoryQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
ChannelId channel_id_;
DialogId sender_dialog_id_;
public:
explicit DeleteParticipantHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, DialogId sender_dialog_id) {
channel_id_ = channel_id;
sender_dialog_id_ = sender_dialog_id;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
auto input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Message sender is not accessible"));
}
send_query(G()->net_query_creator().create(
telegram_api::channels_deleteParticipantHistory(std::move(input_channel), std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteParticipantHistory>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
if (sender_dialog_id_.get_type() != DialogType::Channel) {
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery");
}
promise_.set_error(std::move(status));
}
};
class ReadMentionsQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
DialogId dialog_id_;
public:
explicit ReadMentionsQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId top_thread_message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_readMentions::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_readMentions(flags, std::move(input_peer),
top_thread_message_id.get_server_message_id().get()),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readMentions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadMentionsQuery");
promise_.set_error(std::move(status));
}
};
class ReadReactionsQuery final : public Td::ResultHandler {
Promise<AffectedHistory> promise_;
DialogId dialog_id_;
public:
explicit ReadReactionsQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId top_thread_message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
int32 flags = 0;
if (top_thread_message_id.is_valid()) {
flags |= telegram_api::messages_readReactions::TOP_MSG_ID_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_readReactions(flags, std::move(input_peer),
top_thread_message_id.get_server_message_id().get()),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readReactions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadReactionsQuery");
promise_.set_error(std::move(status));
}
};
class SaveDefaultSendAsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit SaveDefaultSendAsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, DialogId send_as_dialog_id) {
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
auto send_as_input_peer = td_->messages_manager_->get_input_peer(send_as_dialog_id, AccessRights::Read);
CHECK(send_as_input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_saveDefaultSendAs(std::move(input_peer), std::move(send_as_input_peer)),
{{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_saveDefaultSendAs>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto success = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SaveDefaultSendAsQuery: " << success;
promise_.set_value(Unit());
}
void on_error(Status status) final {
// td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDefaultSendAsQuery");
promise_.set_error(std::move(status));
}
};
class SendMessageQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
public:
void send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &text, bool is_copy,
int64 random_id, NetQueryRef *send_query_ref) {
random_id_ = random_id;
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Have no write access to the chat"));
}
if (!entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
if (as_input_peer != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
auto query = G()->net_query_creator().create(
telegram_api::messages_sendMessage(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, std::move(input_peer), reply_to_message_id.get_server_message_id().get(),
top_thread_message_id.get_server_message_id().get(), text, random_id, std::move(reply_markup),
std::move(entities), schedule_date, std::move(as_input_peer)),
{{dialog_id, MessageContentType::Text},
{dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}});
if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
if (result.is_ok()) {
send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
}
});
}
*send_query_ref = query.get_weak();
send_query(std::move(query));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMessage>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendMessageQuery for " << random_id_ << ": " << to_string(ptr);
auto constructor_id = ptr->get_id();
if (constructor_id != telegram_api::updateShortSentMessage::ID) {
td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMessage");
return td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
auto sent_message = move_tl_object_as<telegram_api::updateShortSentMessage>(ptr);
td_->messages_manager_->on_update_sent_text_message(random_id_, std::move(sent_message->media_),
std::move(sent_message->entities_));
auto message_id = MessageId(ServerMessageId(sent_message->id_));
auto ttl_period = sent_message->ttl_period_;
auto update = make_tl_object<updateSentMessage>(random_id_, message_id, sent_message->date_, ttl_period);
if (dialog_id_.get_type() == DialogType::Channel) {
td_->messages_manager_->add_pending_channel_update(dialog_id_, std::move(update), sent_message->pts_,
sent_message->pts_count_, Promise<Unit>(),
"send message actor");
return;
}
td_->updates_manager_->add_pending_pts_update(std::move(update), sent_message->pts_, sent_message->pts_count_,
Time::now(), Promise<Unit>(), "send message actor");
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendMessage: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMessageQuery");
td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
class StartBotQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
public:
NetQueryRef send(tl_object_ptr<telegram_api::InputUser> bot_input_user, DialogId dialog_id,
tl_object_ptr<telegram_api::InputPeer> input_peer, const string &parameter, int64 random_id) {
CHECK(bot_input_user != nullptr);
CHECK(input_peer != nullptr);
random_id_ = random_id;
dialog_id_ = dialog_id;
auto query = G()->net_query_creator().create(
telegram_api::messages_startBot(std::move(bot_input_user), std::move(input_peer), random_id, parameter),
{{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}});
if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
if (result.is_ok()) {
send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
}
});
}
auto send_query_ref = query.get_weak();
send_query(std::move(query));
return send_query_ref;
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_startBot>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for StartBotQuery for " << random_id_ << ": " << to_string(ptr);
// Result may contain messageActionChatAddUser
// td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "StartBot");
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for StartBotQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message should be re-sent
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartBotQuery");
td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
class SendInlineBotResultQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
public:
NetQueryRef send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date, int64 random_id,
int64 query_id, const string &result_id) {
random_id_ = random_id;
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
if (as_input_peer != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
auto query = G()->net_query_creator().create(
telegram_api::messages_sendInlineBotResult(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer),
reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(),
random_id, query_id, result_id, schedule_date, std::move(as_input_peer)),
{{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}});
auto send_query_ref = query.get_weak();
send_query(std::move(query));
return send_query_ref;
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendInlineBotResult>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendInlineBotResultQuery for " << random_id_ << ": " << to_string(ptr);
td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendInlineBotResult");
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendInlineBotResultQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendInlineBotResultQuery");
td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
class SendMultiMediaQuery final : public Td::ResultHandler {
vector<FileId> file_ids_;
vector<string> file_references_;
vector<int64> random_ids_;
DialogId dialog_id_;
public:
void send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date,
vector<FileId> &&file_ids, vector<tl_object_ptr<telegram_api::inputSingleMedia>> &&input_single_media,
bool is_copy) {
for (auto &single_media : input_single_media) {
random_ids_.push_back(single_media->random_id_);
CHECK(FileManager::extract_was_uploaded(single_media->media_) == false);
file_references_.push_back(FileManager::extract_file_reference(single_media->media_));
}
dialog_id_ = dialog_id;
file_ids_ = std::move(file_ids);
CHECK(file_ids_.size() == random_ids_.size());
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Have no write access to the chat"));
}
if (as_input_peer != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
// no quick ack, because file reference errors are very likely to happen
CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
send_query(G()->net_query_creator().create(
telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, false /*ignored*/, std::move(input_peer),
reply_to_message_id.get_server_message_id().get(),
top_thread_message_id.get_server_message_id().get(),
std::move(input_single_media), schedule_date, std::move(as_input_peer)),
{{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo},
{dialog_id, MessageContentType::Photo}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMultiMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr);
auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get());
bool is_result_wrong = false;
auto sent_random_ids_size = sent_random_ids.size();
for (auto &random_id : random_ids_) {
auto it = sent_random_ids.find(random_id);
if (it == sent_random_ids.end()) {
if (random_ids_.size() == 1) {
is_result_wrong = true;
}
td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not sent"));
} else {
sent_random_ids.erase(it);
}
}
if (!sent_random_ids.empty()) {
is_result_wrong = true;
}
if (!is_result_wrong) {
auto sent_messages = UpdatesManager::get_new_messages(ptr.get());
if (sent_random_ids_size != sent_messages.size()) {
is_result_wrong = true;
}
for (auto &sent_message : sent_messages) {
if (DialogId::get_message_dialog_id(sent_message.first) != dialog_id_) {
is_result_wrong = true;
}
}
}
if (is_result_wrong) {
LOG(ERROR) << "Receive wrong result for SendMultiMediaQuery with random_ids " << format::as_array(random_ids_)
<< " to " << dialog_id_ << ": " << oneline(to_string(ptr));
td_->updates_manager_->schedule_get_difference("Wrong sendMultiMedia result");
}
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendMultiMedia: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
auto pos = FileReferenceManager::get_file_reference_error_pos(status);
if (1 <= pos && pos <= file_ids_.size() && file_ids_[pos - 1].is_valid()) {
VLOG(file_references) << "Receive " << status << " for " << file_ids_[pos - 1];
td_->file_manager_->delete_file_reference(file_ids_[pos - 1], file_references_[pos - 1]);
td_->messages_manager_->on_send_media_group_file_reference_error(dialog_id_, std::move(random_ids_));
return;
} else {
LOG(ERROR) << "Receive file reference error " << status << ", but file_ids = " << file_ids_
<< ", message_count = " << file_ids_.size();
}
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMultiMediaQuery");
for (auto &random_id : random_ids_) {
td_->messages_manager_->on_send_message_fail(random_id, status.clone());
}
}
};
class SendMediaQuery final : public Td::ResultHandler {
int64 random_id_ = 0;
FileId file_id_;
FileId thumbnail_file_id_;
DialogId dialog_id_;
string file_reference_;
bool was_uploaded_ = false;
bool was_thumbnail_uploaded_ = false;
public:
void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id,
tl_object_ptr<telegram_api::InputPeer> as_input_peer, MessageId reply_to_message_id,
MessageId top_thread_message_id, int32 schedule_date,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &text,
tl_object_ptr<telegram_api::InputMedia> &&input_media, MessageContentType content_type, bool is_copy,
int64 random_id, NetQueryRef *send_query_ref) {
random_id_ = random_id;
file_id_ = file_id;
thumbnail_file_id_ = thumbnail_file_id;
dialog_id_ = dialog_id;
file_reference_ = FileManager::extract_file_reference(input_media);
was_uploaded_ = FileManager::extract_was_uploaded(input_media);
was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Have no write access to the chat"));
}
if (!entities.empty()) {
flags |= telegram_api::messages_sendMedia::ENTITIES_MASK;
}
if (as_input_peer != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
auto query = G()->net_query_creator().create(
telegram_api::messages_sendMedia(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
std::move(input_peer), reply_to_message_id.get_server_message_id().get(),
top_thread_message_id.get_server_message_id().get(), std::move(input_media), text, random_id,
std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)),
{{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}});
if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) {
query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
if (result.is_ok()) {
send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
}
});
}
*send_query_ref = query.get_weak();
send_query(std::move(query));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
if (was_thumbnail_uploaded_) {
CHECK(thumbnail_file_id_.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
// TODO delete it only in the case it can't be merged with file thumbnail
td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendMediaQuery for " << random_id_ << ": " << to_string(ptr);
td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMedia");
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendMedia: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
if (was_uploaded_) {
if (was_thumbnail_uploaded_) {
CHECK(thumbnail_file_id_.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
td_->messages_manager_->on_send_message_file_part_missing(random_id_,
to_integer<int32>(status.message().substr(10)));
return;
} else {
if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
td_->file_manager_->delete_partial_remote_location(file_id_);
}
}
} else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
if (file_id_.is_valid() && !was_uploaded_) {
VLOG(file_references) << "Receive " << status << " for " << file_id_;
td_->file_manager_->delete_file_reference(file_id_, file_reference_);
td_->messages_manager_->on_send_message_file_reference_error(random_id_);
return;
} else {
LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_
<< ", was_uploaded = " << was_uploaded_;
}
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMediaQuery");
td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
class UploadMediaQuery final : public Td::ResultHandler {
DialogId dialog_id_;
MessageId message_id_;
FileId file_id_;
FileId thumbnail_file_id_;
string file_reference_;
bool was_uploaded_ = false;
bool was_thumbnail_uploaded_ = false;
public:
void send(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputMedia> &&input_media) {
CHECK(input_media != nullptr);
dialog_id_ = dialog_id;
message_id_ = message_id;
file_id_ = file_id;
thumbnail_file_id_ = thumbnail_file_id;
file_reference_ = FileManager::extract_file_reference(input_media);
was_uploaded_ = FileManager::extract_was_uploaded(input_media);
was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Have no write access to the chat"));
}
send_query(G()->net_query_creator().create(
telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_uploadMedia>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
if (was_thumbnail_uploaded_) {
CHECK(thumbnail_file_id_.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": "
<< to_string(ptr);
td_->messages_manager_->on_upload_message_media_success(dialog_id_, message_id_, std::move(ptr));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadMediaQuery");
if (was_uploaded_) {
if (was_thumbnail_uploaded_) {
CHECK(thumbnail_file_id_.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
td_->messages_manager_->on_upload_message_media_file_part_missing(
dialog_id_, message_id_, to_integer<int32>(status.message().substr(10)));
return;
} else {
if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
td_->file_manager_->delete_partial_remote_location(file_id_);
}
}
} else if (FileReferenceManager::is_file_reference_error(status)) {
LOG(ERROR) << "Receive file reference error for UploadMediaQuery";
}
td_->messages_manager_->on_upload_message_media_fail(dialog_id_, message_id_, std::move(status));
}
};
class SendScheduledMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit SendScheduledMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 server_message_id = message_id.get_scheduled_server_message_id().get();
send_query(G()->net_query_creator().create(
telegram_api::messages_sendScheduledMessages(std::move(input_peer), {server_message_id}),
{{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendScheduledMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendScheduledMessageQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendScheduledMessageQuery: " << status;
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScheduledMessageQuery");
promise_.set_error(std::move(status));
}
};
class EditMessageQuery final : public Td::ResultHandler {
Promise<int32> promise_;
DialogId dialog_id_;
MessageId message_id_;
public:
explicit EditMessageQuery(Promise<Unit> &&promise) {
promise_ = PromiseCreator::lambda([promise = std::move(promise)](Result<int32> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(Unit());
}
});
}
explicit EditMessageQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
}
void send(int32 flags, DialogId dialog_id, MessageId message_id, const string &text,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
tl_object_ptr<telegram_api::InputMedia> &&input_media,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup, int32 schedule_date) {
dialog_id_ = dialog_id;
message_id_ = message_id;
if (input_media != nullptr && false) {
return on_error(Status::Error(400, "FILE_PART_1_MISSING"));
}
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
if (reply_markup != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
}
if (!entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
if (!text.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE;
}
if (input_media != nullptr) {
flags |= telegram_api::messages_editMessage::MEDIA_MASK;
}
if (schedule_date != 0) {
flags |= telegram_api::messages_editMessage::SCHEDULE_DATE_MASK;
}
int32 server_message_id = schedule_date != 0 ? message_id.get_scheduled_server_message_id().get()
: message_id.get_server_message_id().get();
send_query(G()->net_query_creator().create(
telegram_api::messages_editMessage(flags, false /*ignored*/, std::move(input_peer), server_message_id, text,
std::move(input_media), std::move(reply_markup), std::move(entities),
schedule_date),
{{dialog_id}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_editMessage>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for EditMessageQuery: " << to_string(ptr);
auto pts = td_->updates_manager_->get_update_edit_message_pts(ptr.get(), {dialog_id_, message_id_});
auto promise = PromiseCreator::lambda(
[promise = std::move(promise_), pts](Result<Unit> result) mutable { promise.set_value(std::move(pts)); });
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for EditMessage: " << status;
if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") {
return promise_.set_value(0);
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditMessageQuery");
promise_.set_error(std::move(status));
}
};
class EditInlineMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit EditInlineMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(int32 flags, tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,
const string &text, vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
tl_object_ptr<telegram_api::InputMedia> &&input_media,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) {
CHECK(input_bot_inline_message_id != nullptr);
// file in an inline message can't be uploaded to another datacenter,
// so only previously uploaded files or URLs can be used in the InputMedia
CHECK(!FileManager::extract_was_uploaded(input_media));
if (reply_markup != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
}
if (!entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
if (!text.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE;
}
if (input_media != nullptr) {
flags |= telegram_api::messages_editInlineBotMessage::MEDIA_MASK;
}
auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
send_query(G()->net_query_creator().create(
telegram_api::messages_editInlineBotMessage(flags, false /*ignored*/, std::move(input_bot_inline_message_id),
text, std::move(input_media), std::move(reply_markup),
std::move(entities)),
{}, dc_id));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_editInlineBotMessage>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of editInlineMessage";
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for EditInlineMessageQuery: " << status;
promise_.set_error(std::move(status));
}
};
class ForwardMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
vector<int64> random_ids_;
DialogId from_dialog_id_;
DialogId to_dialog_id_;
public:
explicit ForwardMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id,
tl_object_ptr<telegram_api::InputPeer> as_input_peer, const vector<MessageId> &message_ids,
vector<int64> &&random_ids, int32 schedule_date) {
random_ids_ = random_ids;
from_dialog_id_ = from_dialog_id;
to_dialog_id_ = to_dialog_id;
auto to_input_peer = td_->messages_manager_->get_input_peer(to_dialog_id, AccessRights::Write);
if (to_input_peer == nullptr) {
return on_error(Status::Error(400, "Have no write access to the chat"));
}
auto from_input_peer = td_->messages_manager_->get_input_peer(from_dialog_id, AccessRights::Read);
if (from_input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat to forward messages from"));
}
if (as_input_peer != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
if (top_thread_message_id.is_valid()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_IS_FROM_THREAD;
}
auto query = G()->net_query_creator().create(
telegram_api::messages_forwardMessages(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, std::move(from_input_peer), MessageId::get_server_message_ids(message_ids),
std::move(random_ids), std::move(to_input_peer), top_thread_message_id.get_server_message_id().get(),
schedule_date, std::move(as_input_peer)),
{{to_dialog_id, MessageContentType::Text}, {to_dialog_id, MessageContentType::Photo}});
if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
query->quick_ack_promise_ = PromiseCreator::lambda([random_ids = random_ids_](Result<Unit> result) {
if (result.is_ok()) {
for (auto random_id : random_ids) {
send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
}
}
});
}
send_query(std::move(query));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_forwardMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for ForwardMessagesQuery for " << format::as_array(random_ids_) << ": "
<< to_string(ptr);
auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get());
bool is_result_wrong = false;
auto sent_random_ids_size = sent_random_ids.size();
for (auto &random_id : random_ids_) {
auto it = sent_random_ids.find(random_id);
if (it == sent_random_ids.end()) {
if (random_ids_.size() == 1) {
is_result_wrong = true;
}
td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not forwarded"));
} else {
sent_random_ids.erase(it);
}
}
if (!sent_random_ids.empty()) {
is_result_wrong = true;
}
if (!is_result_wrong) {
auto sent_messages = UpdatesManager::get_new_messages(ptr.get());
if (sent_random_ids_size != sent_messages.size()) {
is_result_wrong = true;
}
for (auto &sent_message : sent_messages) {
if (DialogId::get_message_dialog_id(sent_message.first) != to_dialog_id_) {
is_result_wrong = true;
}
}
}
if (is_result_wrong) {
LOG(ERROR) << "Receive wrong result for forwarding messages with random_ids " << format::as_array(random_ids_)
<< " to " << to_dialog_id_ << ": " << oneline(to_string(ptr));
td_->updates_manager_->schedule_get_difference("Wrong forwardMessages result");
}
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for forward messages: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, messages should be re-sent
return;
}
// no on_get_dialog_error call, because two dialogs are involved
if (status.code() == 400 && status.message() == CSlice("CHAT_FORWARDS_RESTRICTED")) {
td_->contacts_manager_->reload_dialog_info(from_dialog_id_, Promise<Unit>());
}
if (status.code() == 400 && status.message() == CSlice("SEND_AS_PEER_INVALID")) {
td_->messages_manager_->reload_dialog_info_full(to_dialog_id_, "SEND_AS_PEER_INVALID");
}
for (auto &random_id : random_ids_) {
td_->messages_manager_->on_send_message_fail(random_id, status.clone());
}
promise_.set_error(std::move(status));
}
};
class SendScreenshotNotificationQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
DialogId dialog_id_;
public:
explicit SendScreenshotNotificationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, int64 random_id) {
random_id_ = random_id;
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::messages_sendScreenshotNotification(std::move(input_peer), 0, random_id),
{{dialog_id, MessageContentType::Text}}));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendScreenshotNotification>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendScreenshotNotificationQuery for " << random_id_ << ": " << to_string(ptr);
td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(),
"SendScreenshotNotificationQuery");
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendScreenshotNotificationQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, messages should be re-sent
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScreenshotNotificationQuery");
td_->messages_manager_->on_send_message_fail(random_id_, status.clone());
promise_.set_error(std::move(status));
}
};
class SetTypingQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
int32 generation_ = 0;
public:
explicit SetTypingQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
NetQueryRef send(DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer, MessageId message_id,
tl_object_ptr<telegram_api::SendMessageAction> &&action) {
dialog_id_ = dialog_id;
CHECK(input_peer != nullptr);
int32 flags = 0;
if (message_id.is_valid()) {
flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK;
}
auto query = G()->net_query_creator().create(telegram_api::messages_setTyping(
flags, std::move(input_peer), message_id.get_server_message_id().get(), std::move(action)));
query->total_timeout_limit_ = 2;
auto result = query.get_weak();
generation_ = result.generation();
send_query(std::move(query));
return result;
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setTyping>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
// ignore result
promise_.set_value(Unit());
send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_);
}
void on_error(Status status) final {
if (status.code() == NetQuery::Canceled) {
return promise_.set_value(Unit());
}
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) {
LOG(INFO) << "Receive error for set typing: " << status;
}
promise_.set_error(std::move(status));
send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_);
}
};
class DeleteMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
vector<int32> server_message_ids_;
public:
explicit DeleteMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, vector<int32> &&server_message_ids, bool revoke) {
dialog_id_ = dialog_id;
server_message_ids_ = server_message_ids;
int32 flags = 0;
if (revoke) {
flags |= telegram_api::messages_deleteMessages::REVOKE_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::messages_deleteMessages(flags, false /*ignored*/, std::move(server_message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
if (affected_messages->pts_count_ > 0) {
td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
affected_messages->pts_count_, Time::now(), std::move(promise_),
"delete messages query");
} else {
promise_.set_value(Unit());
}
}
void on_error(Status status) final {
if (!G()->is_expected_error(status)) {
// MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights was removed
// MESSAGE_DELETE_FORBIDDEN can be returned in private chats for bots when revoke time limit exceeded
if (status.message() != "MESSAGE_DELETE_FORBIDDEN" ||
(dialog_id_.get_type() == DialogType::User && !td_->auth_manager_->is_bot())) {
LOG(ERROR) << "Receive error for delete messages: " << status;
}
}
td_->messages_manager_->on_failed_message_deletion(dialog_id_, server_message_ids_);
promise_.set_error(std::move(status));
}
};
class DeleteChannelMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
vector<int32> server_message_ids_;
public:
explicit DeleteChannelMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, vector<int32> &&server_message_ids) {
channel_id_ = channel_id;
server_message_ids_ = server_message_ids;
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
telegram_api::channels_deleteMessages(std::move(input_channel), std::move(server_message_ids))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
if (affected_messages->pts_count_ > 0) {
td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object<dummyUpdate>(),
affected_messages->pts_, affected_messages->pts_count_,
std::move(promise_), "DeleteChannelMessagesQuery");
} else {
promise_.set_value(Unit());
}
}
void on_error(Status status) final {
if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) {
if (status.message() != "MESSAGE_DELETE_FORBIDDEN") {
LOG(ERROR) << "Receive error for delete channel messages: " << status;
}
}
td_->messages_manager_->on_failed_message_deletion(DialogId(channel_id_), server_message_ids_);
promise_.set_error(std::move(status));
}
};
class DeleteScheduledMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
vector<MessageId> message_ids_;
public:
explicit DeleteScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
dialog_id_ = dialog_id;
message_ids_ = std::move(message_ids);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
send_query(G()->net_query_creator().create(telegram_api::messages_deleteScheduledMessages(
std::move(input_peer), MessageId::get_scheduled_server_message_ids(message_ids_))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteScheduledMessages>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for DeleteScheduledMessagesQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteScheduledMessagesQuery")) {
LOG(ERROR) << "Receive error for delete scheduled messages: " << status;
}
td_->messages_manager_->on_failed_scheduled_message_deletion(dialog_id_, message_ids_);
promise_.set_error(std::move(status));
}
};
class GetPeerSettingsQuery final : public Td::ResultHandler {
DialogId dialog_id_;
public:
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_getPeerSettings(std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getPeerSettings>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetPeerSettingsQuery");
td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetPeerSettingsQuery");
td_->messages_manager_->on_get_peer_settings(dialog_id_, std::move(ptr->settings_));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for get peer settings: " << status;
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPeerSettingsQuery");
}
};
class UpdatePeerSettingsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit UpdatePeerSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, bool is_spam_dialog) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise_.set_value(Unit());
}
if (is_spam_dialog) {
send_query(G()->net_query_creator().create(telegram_api::messages_reportSpam(std::move(input_peer))));
} else {
send_query(G()->net_query_creator().create(telegram_api::messages_hidePeerSettingsBar(std::move(input_peer))));
}
}
void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_reportSpam::ReturnType,
telegram_api::messages_hidePeerSettingsBar::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_reportSpam>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object<telegram_api::peerSettings>(), true);
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for update peer settings: " << status;
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdatePeerSettingsQuery");
td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "UpdatePeerSettingsQuery");
promise_.set_error(std::move(status));
}
};
class ReportEncryptedSpamQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit ReportEncryptedSpamQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_encrypted_chat(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_reportEncryptedSpam(std::move(input_peer))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_reportEncryptedSpam>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object<telegram_api::peerSettings>(), true);
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for report encrypted spam: " << status;
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportEncryptedSpamQuery");
td_->messages_manager_->reget_dialog_action_bar(
DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id_.get_secret_chat_id())),
"ReportEncryptedSpamQuery");
promise_.set_error(std::move(status));
}
};
class ReportPeerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit ReportPeerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, const vector<MessageId> &message_ids, ReportReason &&report_reason) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
if (message_ids.empty()) {
send_query(G()->net_query_creator().create(telegram_api::account_reportPeer(
std::move(input_peer), report_reason.get_input_report_reason(), report_reason.get_message())));
} else {
send_query(G()->net_query_creator().create(
telegram_api::messages_report(std::move(input_peer), MessageId::get_server_message_ids(message_ids),
report_reason.get_input_report_reason(), report_reason.get_message())));
}
}
void on_result(BufferSlice packet) final {
static_assert(
std::is_same<telegram_api::account_reportPeer::ReturnType, telegram_api::messages_report::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::account_reportPeer>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
return on_error(Status::Error(400, "Receive false as result"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for report peer: " << status;
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportPeerQuery");
td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ReportPeerQuery");
promise_.set_error(std::move(status));
}
};
class ReportProfilePhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
FileId file_id_;
string file_reference_;
ReportReason report_reason_;
public:
explicit ReportProfilePhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputPhoto> &&input_photo,
ReportReason &&report_reason) {
dialog_id_ = dialog_id;
file_id_ = file_id;
file_reference_ = FileManager::extract_file_reference(input_photo);
report_reason_ = std::move(report_reason);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::account_reportProfilePhoto(
std::move(input_peer), std::move(input_photo), report_reason_.get_input_report_reason(),
report_reason_.get_message())));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::account_reportProfilePhoto>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
return on_error(Status::Error(400, "Receive false as result"));
}
promise_.set_value(Unit());
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for report chat photo: " << status;
if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
VLOG(file_references) << "Receive " << status << " for " << file_id_;
td_->file_manager_->delete_file_reference(file_id_, file_reference_);
td_->file_reference_manager_->repair_file_reference(
file_id_,
PromiseCreator::lambda([dialog_id = dialog_id_, file_id = file_id_, report_reason = std::move(report_reason_),
promise = std::move(promise_)](Result<Unit> result) mutable {
if (result.is_error()) {
LOG(INFO) << "Reported photo " << file_id << " is likely to be deleted";
return promise.set_value(Unit());
}
send_closure(G()->messages_manager(), &MessagesManager::report_dialog_photo, dialog_id, file_id,
std::move(report_reason), std::move(promise));
}));
return;
}
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportProfilePhotoQuery");
promise_.set_error(std::move(status));
}
};
class EditPeerFoldersQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
explicit EditPeerFoldersQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, FolderId folder_id) {
dialog_id_ = dialog_id;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
vector<telegram_api::object_ptr<telegram_api::inputFolderPeer>> input_folder_peers;
input_folder_peers.push_back(
telegram_api::make_object<telegram_api::inputFolderPeer>(std::move(input_peer), folder_id.get()));
send_query(G()->net_query_creator().create(telegram_api::folders_editPeerFolders(std::move(input_folder_peers))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::folders_editPeerFolders>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for EditPeerFoldersQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditPeerFoldersQuery")) {
LOG(INFO) << "Receive error for EditPeerFoldersQuery: " << status;
}
// trying to repair folder ID for this dialog
td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "EditPeerFoldersQuery");
promise_.set_error(std::move(status));
}
};
class GetChannelDifferenceQuery final : public Td::ResultHandler {
DialogId dialog_id_;
int32 pts_;
int32 limit_;
public:
void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputChannel> &&input_channel, int32 pts, int32 limit,
bool force) {
CHECK(pts >= 0);
dialog_id_ = dialog_id;
pts_ = pts;
limit_ = limit;
CHECK(input_channel != nullptr);
int32 flags = 0;
if (force) {
flags |= telegram_api::updates_getChannelDifference::FORCE_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::updates_getChannelDifference(
flags, false /*ignored*/, std::move(input_channel), make_tl_object<telegram_api::channelMessagesFilterEmpty>(),
pts, limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::updates_getChannelDifference>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok());
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery")) {
LOG(ERROR) << "Receive error for GetChannelDifferenceQuery for " << dialog_id_ << " with pts " << pts_
<< " and limit " << limit_ << ": " << status;
}
td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr);
}
};
class ResolveUsernameQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
string username_;
public:
explicit ResolveUsernameQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(const string &username) {
username_ = username;
send_query(G()->net_query_creator().create(telegram_api::contacts_resolveUsername(username)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_resolveUsername>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(DEBUG) << "Receive result for ResolveUsernameQuery: " << to_string(ptr);
td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolveUsernameQuery");
td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolveUsernameQuery");
td_->messages_manager_->on_resolved_username(username_, DialogId(ptr->peer_));
promise_.set_value(Unit());
}
void on_error(Status status) final {
if (status.message() == Slice("USERNAME_NOT_OCCUPIED")) {
td_->messages_manager_->drop_username(username_);
}
promise_.set_error(std::move(status));
}
};
class MessagesManager::UploadMediaCallback final : public FileManager::UploadCallback {
public:
void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, std::move(input_file),
nullptr);
}
void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, nullptr,
std::move(input_file));
}
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media_error, file_id, std::move(error));
}
};
class MessagesManager::UploadThumbnailCallback final : public FileManager::UploadCallback {
public:
void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, std::move(input_file));
}
void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
UNREACHABLE();
}
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, nullptr);
}
};
class MessagesManager::UploadDialogPhotoCallback final : public FileManager::UploadCallback {
public:
void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo, file_id,
std::move(input_file));
}
void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
UNREACHABLE();
}
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo_error, file_id,
std::move(error));
}
};
class MessagesManager::UploadImportedMessagesCallback final : public FileManager::UploadCallback {
public:
void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages, file_id,
std::move(input_file));
}
void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
UNREACHABLE();
}
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages_error, file_id,
std::move(error));
}
};
class MessagesManager::UploadImportedMessageAttachmentCallback final : public FileManager::UploadCallback {
public:
void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment, file_id,
std::move(input_file));
}
void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
UNREACHABLE();
}
void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment_error, file_id,
std::move(error));
}
};
template <class StorerT>
void MessagesManager::Message::store(StorerT &storer) const {
using td::store;
bool has_sender = sender_user_id.is_valid();
bool has_edit_date = edit_date > 0;
bool has_random_id = random_id != 0;
bool is_forwarded = forward_info != nullptr;
bool is_reply = reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled();
bool is_reply_to_random_id = reply_to_random_id != 0;
bool is_via_bot = via_bot_user_id.is_valid();
bool has_view_count = view_count > 0;
bool has_reply_markup = reply_markup != nullptr;
bool has_ttl = ttl != 0;
bool has_author_signature = !author_signature.empty();
bool has_forward_author_signature = is_forwarded && !forward_info->author_signature.empty();
bool has_media_album_id = media_album_id != 0;
bool has_forward_from =
is_forwarded && (forward_info->from_dialog_id.is_valid() || forward_info->from_message_id.is_valid());
bool has_send_date = message_id.is_yet_unsent() && send_date != 0;
bool has_flags2 = true;
bool has_notification_id = notification_id.is_valid();
bool has_forward_sender_name = is_forwarded && !forward_info->sender_name.empty();
bool has_send_error_code = send_error_code != 0;
bool has_real_forward_from = real_forward_from_dialog_id.is_valid() && real_forward_from_message_id.is_valid();
bool has_legacy_layer = legacy_layer != 0;
bool has_restriction_reasons = !restriction_reasons.empty();
bool has_forward_psa_type = is_forwarded && !forward_info->psa_type.empty();
bool has_forward_count = forward_count > 0;
bool has_reply_info = !reply_info.is_empty();
bool has_sender_dialog_id = sender_dialog_id.is_valid();
bool has_reply_in_dialog_id = is_reply && reply_in_dialog_id.is_valid();
bool has_top_thread_message_id = top_thread_message_id.is_valid();
bool has_thread_draft_message = thread_draft_message != nullptr;
bool has_local_thread_message_ids = !local_thread_message_ids.empty();
bool has_linked_top_thread_message_id = linked_top_thread_message_id.is_valid();
bool has_interaction_info_update_date = interaction_info_update_date != 0;
bool has_send_emoji = !send_emoji.empty();
bool is_imported = is_forwarded && forward_info->is_imported;
bool has_ttl_period = ttl_period != 0;
bool has_max_reply_media_timestamp = max_reply_media_timestamp >= 0;
bool are_message_media_timestamp_entities_found = true;
bool has_flags3 = true;
bool has_reactions = reactions != nullptr;
bool has_available_reactions_generation = available_reactions_generation != 0;
bool has_history_generation = history_generation != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_channel_post);
STORE_FLAG(is_outgoing);
STORE_FLAG(is_failed_to_send);
STORE_FLAG(disable_notification);
STORE_FLAG(contains_mention);
STORE_FLAG(from_background);
STORE_FLAG(disable_web_page_preview);
STORE_FLAG(clear_draft);
STORE_FLAG(have_previous);
STORE_FLAG(have_next);
STORE_FLAG(has_sender);
STORE_FLAG(has_edit_date);
STORE_FLAG(has_random_id);
STORE_FLAG(is_forwarded);
STORE_FLAG(is_reply);
STORE_FLAG(is_reply_to_random_id);
STORE_FLAG(is_via_bot);
STORE_FLAG(has_view_count);
STORE_FLAG(has_reply_markup);
STORE_FLAG(has_ttl);
STORE_FLAG(has_author_signature);
STORE_FLAG(has_forward_author_signature);
STORE_FLAG(had_reply_markup);
STORE_FLAG(contains_unread_mention);
STORE_FLAG(has_media_album_id);
STORE_FLAG(has_forward_from);
STORE_FLAG(in_game_share);
STORE_FLAG(is_content_secret);
STORE_FLAG(has_send_date);
STORE_FLAG(has_flags2);
END_STORE_FLAGS();
if (has_flags2) {
BEGIN_STORE_FLAGS();
STORE_FLAG(has_notification_id);
STORE_FLAG(is_mention_notification_disabled);
STORE_FLAG(had_forward_info);
STORE_FLAG(has_forward_sender_name);
STORE_FLAG(has_send_error_code);
STORE_FLAG(hide_via_bot);
STORE_FLAG(is_bot_start_message);
STORE_FLAG(has_real_forward_from);
STORE_FLAG(has_legacy_layer);
STORE_FLAG(hide_edit_date);
STORE_FLAG(has_restriction_reasons);
STORE_FLAG(is_from_scheduled);
STORE_FLAG(is_copy);
STORE_FLAG(has_forward_psa_type);
STORE_FLAG(has_forward_count);
STORE_FLAG(has_reply_info);
STORE_FLAG(has_sender_dialog_id);
STORE_FLAG(has_reply_in_dialog_id);
STORE_FLAG(has_top_thread_message_id);
STORE_FLAG(has_thread_draft_message);
STORE_FLAG(has_local_thread_message_ids);
STORE_FLAG(has_linked_top_thread_message_id);
STORE_FLAG(is_pinned);
STORE_FLAG(has_interaction_info_update_date);
STORE_FLAG(has_send_emoji);
STORE_FLAG(is_imported);
STORE_FLAG(has_ttl_period);
STORE_FLAG(has_max_reply_media_timestamp);
STORE_FLAG(are_message_media_timestamp_entities_found);
STORE_FLAG(has_flags3);
END_STORE_FLAGS();
}
if (has_flags3) {
BEGIN_STORE_FLAGS();
STORE_FLAG(noforwards);
STORE_FLAG(has_explicit_sender);
STORE_FLAG(has_reactions);
STORE_FLAG(has_available_reactions_generation);
STORE_FLAG(update_stickersets_order);
STORE_FLAG(is_topic_message);
STORE_FLAG(has_history_generation);
END_STORE_FLAGS();
}
store(message_id, storer);
if (has_sender) {
store(sender_user_id, storer);
}
store(date, storer);
if (has_edit_date) {
store(edit_date, storer);
}
if (has_send_date) {
store(send_date, storer);
}
if (has_random_id) {
store(random_id, storer);
}
if (is_forwarded) {
store(forward_info->sender_user_id, storer);
store(forward_info->date, storer);
store(forward_info->sender_dialog_id, storer);
store(forward_info->message_id, storer);
if (has_forward_author_signature) {
store(forward_info->author_signature, storer);
}
if (has_forward_sender_name) {
store(forward_info->sender_name, storer);
}
if (has_forward_from) {
store(forward_info->from_dialog_id, storer);
store(forward_info->from_message_id, storer);
}
if (has_forward_psa_type) {
store(forward_info->psa_type, storer);
}
}
if (has_real_forward_from) {
store(real_forward_from_dialog_id, storer);
store(real_forward_from_message_id, storer);
}
if (is_reply) {
store(reply_to_message_id, storer);
}
if (is_reply_to_random_id) {
store(reply_to_random_id, storer);
}
if (is_via_bot) {
store(via_bot_user_id, storer);
}
if (has_view_count) {
store(view_count, storer);
}
if (has_forward_count) {
store(forward_count, storer);
}
if (has_reply_info) {
store(reply_info, storer);
}
if (has_ttl) {
store(ttl, storer);
store_time(ttl_expires_at, storer);
}
if (has_send_error_code) {
store(send_error_code, storer);
store(send_error_message, storer);
if (send_error_code == 429) {
store_time(try_resend_at, storer);
}
}
if (has_author_signature) {
store(author_signature, storer);
}
if (has_media_album_id) {
store(media_album_id, storer);
}
if (has_notification_id) {
store(notification_id, storer);
}
if (has_legacy_layer) {
store(legacy_layer, storer);
}
if (has_restriction_reasons) {
store(restriction_reasons, storer);
}
if (has_sender_dialog_id) {
store(sender_dialog_id, storer);
}
if (has_reply_in_dialog_id) {
store(reply_in_dialog_id, storer);
}
if (has_top_thread_message_id) {
store(top_thread_message_id, storer);
}
if (has_thread_draft_message) {
store(thread_draft_message, storer);
}
if (has_local_thread_message_ids) {
store(local_thread_message_ids, storer);
}
if (has_linked_top_thread_message_id) {
store(linked_top_thread_message_id, storer);
}
if (has_interaction_info_update_date) {
store(interaction_info_update_date, storer);
}
if (has_send_emoji) {
store(send_emoji, storer);
}
store_message_content(content.get(), storer);
if (has_reply_markup) {
store(reply_markup, storer);
}
if (has_ttl_period) {
store(ttl_period, storer);
}
if (has_max_reply_media_timestamp) {
store(max_reply_media_timestamp, storer);
}
if (has_reactions) {
store(reactions, storer);
}
if (has_available_reactions_generation) {
store(available_reactions_generation, storer);
}
if (has_history_generation) {
store(history_generation, storer);
}
}
// do not forget to resolve message dependencies
template <class ParserT>
void MessagesManager::Message::parse(ParserT &parser) {
using td::parse;
bool has_sender;
bool has_edit_date;
bool has_random_id;
bool is_forwarded;
bool is_reply;
bool is_reply_to_random_id;
bool is_via_bot;
bool has_view_count;
bool has_reply_markup;
bool has_ttl;
bool has_author_signature;
bool has_forward_author_signature;
bool has_media_album_id;
bool has_forward_from;
bool has_send_date;
bool has_flags2;
bool has_notification_id = false;
bool has_forward_sender_name = false;
bool has_send_error_code = false;
bool has_real_forward_from = false;
bool has_legacy_layer = false;
bool has_restriction_reasons = false;
bool has_forward_psa_type = false;
bool has_forward_count = false;
bool has_reply_info = false;
bool has_sender_dialog_id = false;
bool has_reply_in_dialog_id = false;
bool has_top_thread_message_id = false;
bool has_thread_draft_message = false;
bool has_local_thread_message_ids = false;
bool has_linked_top_thread_message_id = false;
bool has_interaction_info_update_date = false;
bool has_send_emoji = false;
bool is_imported = false;
bool has_ttl_period = false;
bool has_max_reply_media_timestamp = false;
bool has_flags3 = false;
bool has_reactions = false;
bool has_available_reactions_generation = false;
bool has_history_generation = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_channel_post);
PARSE_FLAG(is_outgoing);
PARSE_FLAG(is_failed_to_send);
PARSE_FLAG(disable_notification);
PARSE_FLAG(contains_mention);
PARSE_FLAG(from_background);
PARSE_FLAG(disable_web_page_preview);
PARSE_FLAG(clear_draft);
PARSE_FLAG(have_previous);
PARSE_FLAG(have_next);
PARSE_FLAG(has_sender);
PARSE_FLAG(has_edit_date);
PARSE_FLAG(has_random_id);
PARSE_FLAG(is_forwarded);
PARSE_FLAG(is_reply);
PARSE_FLAG(is_reply_to_random_id);
PARSE_FLAG(is_via_bot);
PARSE_FLAG(has_view_count);
PARSE_FLAG(has_reply_markup);
PARSE_FLAG(has_ttl);
PARSE_FLAG(has_author_signature);
PARSE_FLAG(has_forward_author_signature);
PARSE_FLAG(had_reply_markup);
PARSE_FLAG(contains_unread_mention);
PARSE_FLAG(has_media_album_id);
PARSE_FLAG(has_forward_from);
PARSE_FLAG(in_game_share);
PARSE_FLAG(is_content_secret);
PARSE_FLAG(has_send_date);
PARSE_FLAG(has_flags2);
END_PARSE_FLAGS();
if (has_flags2) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_notification_id);
PARSE_FLAG(is_mention_notification_disabled);
PARSE_FLAG(had_forward_info);
PARSE_FLAG(has_forward_sender_name);
PARSE_FLAG(has_send_error_code);
PARSE_FLAG(hide_via_bot);
PARSE_FLAG(is_bot_start_message);
PARSE_FLAG(has_real_forward_from);
PARSE_FLAG(has_legacy_layer);
PARSE_FLAG(hide_edit_date);
PARSE_FLAG(has_restriction_reasons);
PARSE_FLAG(is_from_scheduled);
PARSE_FLAG(is_copy);
PARSE_FLAG(has_forward_psa_type);
PARSE_FLAG(has_forward_count);
PARSE_FLAG(has_reply_info);
PARSE_FLAG(has_sender_dialog_id);
PARSE_FLAG(has_reply_in_dialog_id);
PARSE_FLAG(has_top_thread_message_id);
PARSE_FLAG(has_thread_draft_message);
PARSE_FLAG(has_local_thread_message_ids);
PARSE_FLAG(has_linked_top_thread_message_id);
PARSE_FLAG(is_pinned);
PARSE_FLAG(has_interaction_info_update_date);
PARSE_FLAG(has_send_emoji);
PARSE_FLAG(is_imported);
PARSE_FLAG(has_ttl_period);
PARSE_FLAG(has_max_reply_media_timestamp);
PARSE_FLAG(are_media_timestamp_entities_found);
PARSE_FLAG(has_flags3);
END_PARSE_FLAGS();
}
if (has_flags3) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(noforwards);
PARSE_FLAG(has_explicit_sender);
PARSE_FLAG(has_reactions);
PARSE_FLAG(has_available_reactions_generation);
PARSE_FLAG(update_stickersets_order);
PARSE_FLAG(is_topic_message);
PARSE_FLAG(has_history_generation);
END_PARSE_FLAGS();
}
parse(message_id, parser);
random_y = get_random_y(message_id);
if (has_sender) {
parse(sender_user_id, parser);
}
parse(date, parser);
if (has_edit_date) {
parse(edit_date, parser);
}
if (has_send_date) {
CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
CHECK(message_id.is_yet_unsent());
parse(send_date, parser);
} else if (message_id.is_valid() && message_id.is_yet_unsent()) {
send_date = date; // for backward compatibility
}
if (has_random_id) {
parse(random_id, parser);
}
if (is_forwarded) {
forward_info = make_unique<MessageForwardInfo>();
parse(forward_info->sender_user_id, parser);
parse(forward_info->date, parser);
parse(forward_info->sender_dialog_id, parser);
parse(forward_info->message_id, parser);
if (has_forward_author_signature) {
parse(forward_info->author_signature, parser);
}
if (has_forward_sender_name) {
parse(forward_info->sender_name, parser);
}
if (has_forward_from) {
parse(forward_info->from_dialog_id, parser);
parse(forward_info->from_message_id, parser);
}
if (has_forward_psa_type) {
parse(forward_info->psa_type, parser);
}
forward_info->is_imported = is_imported;
}
if (has_real_forward_from) {
parse(real_forward_from_dialog_id, parser);
parse(real_forward_from_message_id, parser);
}
if (is_reply) {
parse(reply_to_message_id, parser);
}
if (is_reply_to_random_id) {
parse(reply_to_random_id, parser);
}
if (is_via_bot) {
parse(via_bot_user_id, parser);
}
if (has_view_count) {
parse(view_count, parser);
}
if (has_forward_count) {
parse(forward_count, parser);
}
if (has_reply_info) {
parse(reply_info, parser);
}
if (has_ttl) {
parse(ttl, parser);
parse_time(ttl_expires_at, parser);
}
if (has_send_error_code) {
parse(send_error_code, parser);
parse(send_error_message, parser);
if (send_error_code == 429) {
parse_time(try_resend_at, parser);
}
}
if (has_author_signature) {
parse(author_signature, parser);
}
if (has_media_album_id) {
parse(media_album_id, parser);
}
if (has_notification_id) {
parse(notification_id, parser);
}
if (has_legacy_layer) {
parse(legacy_layer, parser);
}
if (has_restriction_reasons) {
parse(restriction_reasons, parser);
}
if (has_sender_dialog_id) {
parse(sender_dialog_id, parser);
}
if (has_reply_in_dialog_id) {
parse(reply_in_dialog_id, parser);
}
if (has_top_thread_message_id) {
parse(top_thread_message_id, parser);
}
if (has_thread_draft_message) {
parse(thread_draft_message, parser);
}
if (has_local_thread_message_ids) {
parse(local_thread_message_ids, parser);
}
if (has_linked_top_thread_message_id) {
parse(linked_top_thread_message_id, parser);
}
if (has_interaction_info_update_date) {
parse(interaction_info_update_date, parser);
}
if (has_send_emoji) {
parse(send_emoji, parser);
}
parse_message_content(content, parser);
if (has_reply_markup) {
parse(reply_markup, parser);
}
if (has_ttl_period) {
parse(ttl_period, parser);
}
if (has_max_reply_media_timestamp) {
parse(max_reply_media_timestamp, parser);
}
if (has_reactions) {
parse(reactions, parser);
}
if (has_available_reactions_generation) {
parse(available_reactions_generation, parser);
}
if (has_history_generation) {
parse(history_generation, parser);
}
CHECK(content != nullptr);
is_content_secret |=
is_secret_message_content(ttl, content->get_type()); // repair is_content_secret for old messages
if (hide_edit_date && content->get_type() == MessageContentType::LiveLocation) {
hide_edit_date = false;
}
}
template <class StorerT>
void MessagesManager::NotificationGroupInfo::store(StorerT &storer) const {
using td::store;
store(group_id, storer);
store(last_notification_date, storer);
store(last_notification_id, storer);
store(max_removed_notification_id, storer);
store(max_removed_message_id, storer);
}
template <class ParserT>
void MessagesManager::NotificationGroupInfo::parse(ParserT &parser) {
using td::parse;
parse(group_id, parser);
parse(last_notification_date, parser);
parse(last_notification_id, parser);
parse(max_removed_notification_id, parser);
if (parser.version() >= static_cast<int32>(Version::AddNotificationGroupInfoMaxRemovedMessageId)) {
parse(max_removed_message_id, parser);
}
}
template <class StorerT>
void MessagesManager::Dialog::store(StorerT &storer) const {
using td::store;
const Message *last_database_message = nullptr;
if (last_database_message_id.is_valid()) {
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();
bool has_first_database_message_id_by_index = true;
bool has_message_count_by_index = true;
bool has_client_data = !client_data.empty();
bool has_last_read_all_mentions_message_id = last_read_all_mentions_message_id.is_valid();
bool has_max_unavailable_message_id = max_unavailable_message_id.is_valid();
bool has_local_unread_count = local_unread_count != 0;
bool has_deleted_last_message = delete_last_message_date > 0;
bool has_last_clear_history_message_id = last_clear_history_message_id.is_valid();
bool has_last_database_message_id = !has_last_database_message && last_database_message_id.is_valid();
bool has_message_notification_group =
message_notification_group.group_id.is_valid() && !message_notification_group.try_reuse;
bool has_mention_notification_group =
mention_notification_group.group_id.is_valid() && !mention_notification_group.try_reuse;
bool has_new_secret_chat_notification_id = new_secret_chat_notification_id.is_valid();
bool has_pinned_message_notification = pinned_message_notification_message_id.is_valid();
bool has_last_pinned_message_id = last_pinned_message_id.is_valid();
bool has_flags2 = true;
bool has_max_notification_message_id =
max_notification_message_id.is_valid() && max_notification_message_id > last_new_message_id;
bool has_folder_id = folder_id != FolderId();
bool has_pending_read_channel_inbox = pending_read_channel_inbox_pts != 0;
bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent();
bool has_active_group_call_id = active_group_call_id.is_valid();
bool has_message_ttl = !message_ttl.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;
bool has_theme_name = !theme_name.empty();
bool has_flags3 = true;
bool has_pending_join_requests = pending_join_request_count != 0;
bool has_action_bar = action_bar != nullptr;
bool has_default_send_message_as_dialog_id = default_send_message_as_dialog_id.is_valid();
bool has_legacy_available_reactions = false;
bool has_available_reactions_generation = available_reactions_generation != 0;
bool has_have_full_history_source = have_full_history && have_full_history_source != 0;
bool has_available_reactions = !available_reactions.empty();
bool has_history_generation = history_generation != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_draft_message);
STORE_FLAG(has_last_database_message);
STORE_FLAG(false); // legacy_know_can_report_spam
STORE_FLAG(false); // action_bar->can_report_spam
STORE_FLAG(has_first_database_message_id);
STORE_FLAG(false); // legacy_is_pinned
STORE_FLAG(has_first_database_message_id_by_index);
STORE_FLAG(has_message_count_by_index);
STORE_FLAG(has_client_data);
STORE_FLAG(need_restore_reply_markup);
STORE_FLAG(have_full_history);
STORE_FLAG(has_last_read_all_mentions_message_id);
STORE_FLAG(has_max_unavailable_message_id);
STORE_FLAG(is_last_read_inbox_message_id_inited);
STORE_FLAG(is_last_read_outbox_message_id_inited);
STORE_FLAG(has_local_unread_count);
STORE_FLAG(has_deleted_last_message);
STORE_FLAG(has_last_clear_history_message_id);
STORE_FLAG(is_last_message_deleted_locally);
STORE_FLAG(has_contact_registered_message);
STORE_FLAG(has_last_database_message_id);
STORE_FLAG(need_repair_server_unread_count);
STORE_FLAG(is_marked_as_unread);
STORE_FLAG(has_message_notification_group);
STORE_FLAG(has_mention_notification_group);
STORE_FLAG(has_new_secret_chat_notification_id);
STORE_FLAG(has_pinned_message_notification);
STORE_FLAG(has_last_pinned_message_id);
STORE_FLAG(is_last_pinned_message_id_inited);
STORE_FLAG(has_flags2);
END_STORE_FLAGS();
store(dialog_id, storer); // must be stored at offset 4
if (has_flags2) {
BEGIN_STORE_FLAGS();
STORE_FLAG(has_max_notification_message_id);
STORE_FLAG(has_folder_id);
STORE_FLAG(is_folder_id_inited);
STORE_FLAG(has_pending_read_channel_inbox);
STORE_FLAG(know_action_bar);
STORE_FLAG(false); // action_bar->can_add_contact
STORE_FLAG(false); // action_bar->can_block_user
STORE_FLAG(false); // action_bar->can_share_phone_number
STORE_FLAG(false); // action_bar->can_report_location
STORE_FLAG(has_scheduled_server_messages);
STORE_FLAG(has_scheduled_database_messages);
STORE_FLAG(need_repair_channel_server_unread_count);
STORE_FLAG(false); // action_bar->can_unarchive
STORE_FLAG(false); // action_bar_has_distance
STORE_FLAG(has_outgoing_messages);
STORE_FLAG(has_last_yet_unsent_message);
STORE_FLAG(is_blocked);
STORE_FLAG(is_is_blocked_inited);
STORE_FLAG(has_active_group_call);
STORE_FLAG(is_group_call_empty);
STORE_FLAG(has_active_group_call_id);
STORE_FLAG(false); // action_bar->can_invite_members
STORE_FLAG(has_message_ttl);
STORE_FLAG(is_message_ttl_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);
STORE_FLAG(is_theme_name_inited);
STORE_FLAG(has_theme_name);
STORE_FLAG(has_flags3);
END_STORE_FLAGS();
}
if (has_flags3) {
BEGIN_STORE_FLAGS();
STORE_FLAG(has_pending_join_requests);
STORE_FLAG(need_repair_action_bar);
STORE_FLAG(has_action_bar);
STORE_FLAG(has_default_send_message_as_dialog_id);
STORE_FLAG(need_drop_default_send_message_as_dialog_id);
STORE_FLAG(has_legacy_available_reactions);
STORE_FLAG(is_available_reactions_inited);
STORE_FLAG(has_available_reactions_generation);
STORE_FLAG(has_have_full_history_source);
STORE_FLAG(has_available_reactions);
STORE_FLAG(has_history_generation);
END_STORE_FLAGS();
}
store(last_new_message_id, storer);
store(server_unread_count, storer);
if (has_local_unread_count) {
store(local_unread_count, storer);
}
store(last_read_inbox_message_id, storer);
store(last_read_outbox_message_id, storer);
store(reply_markup_message_id, storer);
store(notification_settings, storer);
if (has_draft_message) {
store(draft_message, storer);
}
store(last_clear_history_date, storer);
store(order, storer);
if (has_last_database_message) {
store(*last_database_message, storer);
}
if (has_first_database_message_id) {
store(first_database_message_id, storer);
}
if (has_deleted_last_message) {
store(delete_last_message_date, storer);
store(deleted_last_message_id, storer);
}
if (has_last_clear_history_message_id) {
store(last_clear_history_message_id, storer);
}
if (has_first_database_message_id_by_index) {
store(static_cast<int32>(first_database_message_id_by_index.size()), storer);
for (auto first_message_id : first_database_message_id_by_index) {
store(first_message_id, storer);
}
}
if (has_message_count_by_index) {
store(static_cast<int32>(message_count_by_index.size()), storer);
for (auto message_count : message_count_by_index) {
store(message_count, storer);
}
}
if (has_client_data) {
store(client_data, storer);
}
if (has_last_read_all_mentions_message_id) {
store(last_read_all_mentions_message_id, storer);
}
if (has_max_unavailable_message_id) {
store(max_unavailable_message_id, storer);
}
if (has_last_database_message_id) {
store(last_database_message_id, storer);
}
if (has_message_notification_group) {
store(message_notification_group, storer);
}
if (has_mention_notification_group) {
store(mention_notification_group, storer);
}
if (has_new_secret_chat_notification_id) {
store(new_secret_chat_notification_id, storer);
}
if (has_pinned_message_notification) {
store(pinned_message_notification_message_id, storer);
}
if (has_last_pinned_message_id) {
store(last_pinned_message_id, storer);
}
if (has_max_notification_message_id) {
store(max_notification_message_id, storer);
}
if (has_folder_id) {
store(folder_id, storer);
}
if (has_pending_read_channel_inbox) {
store(pending_read_channel_inbox_pts, storer);
store(pending_read_channel_inbox_max_message_id, storer);
store(pending_read_channel_inbox_server_unread_count, storer);
}
if (has_active_group_call_id) {
store(active_group_call_id, storer);
}
if (has_message_ttl) {
store(message_ttl, storer);
}
if (has_default_join_group_call_as_dialog_id) {
store(default_join_group_call_as_dialog_id, storer);
}
if (has_theme_name) {
store(theme_name, storer);
}
if (has_pending_join_requests) {
store(pending_join_request_count, storer);
store(pending_join_request_user_ids, storer);
}
if (has_action_bar) {
store(action_bar, storer);
}
if (has_default_send_message_as_dialog_id) {
store(default_send_message_as_dialog_id, storer);
}
if (has_available_reactions) {
store(available_reactions, storer);
}
if (has_available_reactions_generation) {
store(available_reactions_generation, storer);
}
if (has_have_full_history_source) {
store(have_full_history_source, storer);
}
if (has_history_generation) {
store(history_generation, storer);
}
}
// do not forget to resolve dialog dependencies including dependencies of last_message
template <class ParserT>
void MessagesManager::Dialog::parse(ParserT &parser) {
using td::parse;
bool has_draft_message;
bool has_last_database_message;
bool legacy_know_can_report_spam;
bool has_first_database_message_id;
bool legacy_is_pinned;
bool has_first_database_message_id_by_index;
bool has_message_count_by_index;
bool has_client_data;
bool has_last_read_all_mentions_message_id;
bool has_max_unavailable_message_id;
bool has_local_unread_count;
bool has_deleted_last_message;
bool has_last_clear_history_message_id;
bool has_last_database_message_id;
bool has_message_notification_group;
bool has_mention_notification_group;
bool has_new_secret_chat_notification_id;
bool has_pinned_message_notification;
bool has_last_pinned_message_id;
bool has_flags2;
bool has_max_notification_message_id = false;
bool has_folder_id = false;
bool has_pending_read_channel_inbox = false;
bool has_active_group_call_id = false;
bool has_message_ttl = false;
bool has_default_join_group_call_as_dialog_id = false;
bool has_theme_name = false;
bool has_flags3 = false;
bool has_pending_join_requests = false;
bool action_bar_can_report_spam = false;
bool action_bar_can_add_contact = false;
bool action_bar_can_block_user = false;
bool action_bar_can_share_phone_number = false;
bool action_bar_can_report_location = false;
bool action_bar_can_unarchive = false;
bool action_bar_has_distance = false;
bool action_bar_can_invite_members = false;
bool has_action_bar = false;
bool has_default_send_message_as_dialog_id = false;
bool has_legacy_available_reactions = false;
bool has_available_reactions_generation = false;
bool has_have_full_history_source = false;
bool has_available_reactions = false;
bool has_history_generation = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_draft_message);
PARSE_FLAG(has_last_database_message);
PARSE_FLAG(legacy_know_can_report_spam);
PARSE_FLAG(action_bar_can_report_spam);
PARSE_FLAG(has_first_database_message_id);
PARSE_FLAG(legacy_is_pinned);
PARSE_FLAG(has_first_database_message_id_by_index);
PARSE_FLAG(has_message_count_by_index);
PARSE_FLAG(has_client_data);
PARSE_FLAG(need_restore_reply_markup);
PARSE_FLAG(have_full_history);
PARSE_FLAG(has_last_read_all_mentions_message_id);
PARSE_FLAG(has_max_unavailable_message_id);
PARSE_FLAG(is_last_read_inbox_message_id_inited);
PARSE_FLAG(is_last_read_outbox_message_id_inited);
PARSE_FLAG(has_local_unread_count);
PARSE_FLAG(has_deleted_last_message);
PARSE_FLAG(has_last_clear_history_message_id);
PARSE_FLAG(is_last_message_deleted_locally);
PARSE_FLAG(has_contact_registered_message);
PARSE_FLAG(has_last_database_message_id);
PARSE_FLAG(need_repair_server_unread_count);
PARSE_FLAG(is_marked_as_unread);
PARSE_FLAG(has_message_notification_group);
PARSE_FLAG(has_mention_notification_group);
PARSE_FLAG(has_new_secret_chat_notification_id);
PARSE_FLAG(has_pinned_message_notification);
PARSE_FLAG(has_last_pinned_message_id);
PARSE_FLAG(is_last_pinned_message_id_inited);
PARSE_FLAG(has_flags2);
END_PARSE_FLAGS();
parse(dialog_id, parser); // must be stored at offset 4
if (has_flags2) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_max_notification_message_id);
PARSE_FLAG(has_folder_id);
PARSE_FLAG(is_folder_id_inited);
PARSE_FLAG(has_pending_read_channel_inbox);
PARSE_FLAG(know_action_bar);
PARSE_FLAG(action_bar_can_add_contact);
PARSE_FLAG(action_bar_can_block_user);
PARSE_FLAG(action_bar_can_share_phone_number);
PARSE_FLAG(action_bar_can_report_location);
PARSE_FLAG(has_scheduled_server_messages);
PARSE_FLAG(has_scheduled_database_messages);
PARSE_FLAG(need_repair_channel_server_unread_count);
PARSE_FLAG(action_bar_can_unarchive);
PARSE_FLAG(action_bar_has_distance);
PARSE_FLAG(has_outgoing_messages);
PARSE_FLAG(had_last_yet_unsent_message);
PARSE_FLAG(is_blocked);
PARSE_FLAG(is_is_blocked_inited);
PARSE_FLAG(has_active_group_call);
PARSE_FLAG(is_group_call_empty);
PARSE_FLAG(has_active_group_call_id);
PARSE_FLAG(action_bar_can_invite_members);
PARSE_FLAG(has_message_ttl);
PARSE_FLAG(is_message_ttl_inited);
PARSE_FLAG(has_default_join_group_call_as_dialog_id);
PARSE_FLAG(has_bots);
PARSE_FLAG(is_has_bots_inited);
PARSE_FLAG(is_theme_name_inited);
PARSE_FLAG(has_theme_name);
PARSE_FLAG(has_flags3);
END_PARSE_FLAGS();
} else {
is_folder_id_inited = false;
has_scheduled_server_messages = false;
has_scheduled_database_messages = false;
need_repair_channel_server_unread_count = false;
has_outgoing_messages = false;
had_last_yet_unsent_message = false;
is_blocked = false;
is_is_blocked_inited = false;
has_active_group_call = false;
is_group_call_empty = false;
is_message_ttl_inited = false;
has_bots = false;
is_has_bots_inited = false;
is_theme_name_inited = false;
}
if (has_flags3) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_pending_join_requests);
PARSE_FLAG(need_repair_action_bar);
PARSE_FLAG(has_action_bar);
PARSE_FLAG(has_default_send_message_as_dialog_id);
PARSE_FLAG(need_drop_default_send_message_as_dialog_id);
PARSE_FLAG(has_legacy_available_reactions);
PARSE_FLAG(is_available_reactions_inited);
PARSE_FLAG(has_available_reactions_generation);
PARSE_FLAG(has_have_full_history_source);
PARSE_FLAG(has_available_reactions);
PARSE_FLAG(has_history_generation);
END_PARSE_FLAGS();
} else {
need_repair_action_bar = false;
is_available_reactions_inited = false;
}
parse(last_new_message_id, parser);
parse(server_unread_count, parser);
if (has_local_unread_count) {
parse(local_unread_count, parser);
}
parse(last_read_inbox_message_id, parser);
if (last_read_inbox_message_id.is_valid()) {
is_last_read_inbox_message_id_inited = true;
}
parse(last_read_outbox_message_id, parser);
if (last_read_outbox_message_id.is_valid()) {
is_last_read_outbox_message_id_inited = true;
}
parse(reply_markup_message_id, parser);
parse(notification_settings, parser);
if (has_draft_message) {
parse(draft_message, parser);
}
parse(last_clear_history_date, parser);
parse(order, parser);
if (has_last_database_message) {
parse(messages, parser);
}
if (has_first_database_message_id) {
parse(first_database_message_id, parser);
}
if (legacy_is_pinned) {
int64 legacy_pinned_order;
parse(legacy_pinned_order, parser);
}
if (has_deleted_last_message) {
parse(delete_last_message_date, parser);
parse(deleted_last_message_id, parser);
}
if (has_last_clear_history_message_id) {
parse(last_clear_history_message_id, parser);
}
if (has_first_database_message_id_by_index) {
int32 size;
parse(size, parser);
if (size < 0) {
// the log event is broken
// it should be impossible, but has happenned at least once
parser.set_error("Wrong first_database_message_id_by_index table size");
return;
}
LOG_CHECK(static_cast<size_t>(size) <= first_database_message_id_by_index.size())
<< size << " " << first_database_message_id_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(first_database_message_id_by_index[i], parser);
}
}
if (has_message_count_by_index) {
int32 size;
parse(size, parser);
if (size < 0) {
// the log event is broken
// it should be impossible, but has happenned at least once
parser.set_error("Wrong message_count_by_index table size");
return;
}
LOG_CHECK(static_cast<size_t>(size) <= message_count_by_index.size())
<< size << " " << message_count_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(message_count_by_index[i], parser);
}
}
unread_mention_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)];
LOG(INFO) << "Set unread mention message count in " << dialog_id << " to " << unread_mention_count;
if (unread_mention_count < 0) {
unread_mention_count = 0;
}
unread_reaction_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)];
LOG(INFO) << "Set unread reaction count in " << dialog_id << " to " << unread_reaction_count;
if (unread_reaction_count < 0) {
unread_reaction_count = 0;
}
if (has_client_data) {
parse(client_data, parser);
}
if (has_last_read_all_mentions_message_id) {
parse(last_read_all_mentions_message_id, parser);
}
if (has_max_unavailable_message_id) {
parse(max_unavailable_message_id, parser);
}
if (has_last_database_message_id) {
parse(last_database_message_id, parser);
}
if (has_message_notification_group) {
parse(message_notification_group, parser);
}
if (has_mention_notification_group) {
parse(mention_notification_group, parser);
}
if (has_new_secret_chat_notification_id) {
parse(new_secret_chat_notification_id, parser);
}
if (has_pinned_message_notification) {
parse(pinned_message_notification_message_id, parser);
}
if (has_last_pinned_message_id) {
parse(last_pinned_message_id, parser);
}
if (has_max_notification_message_id) {
parse(max_notification_message_id, parser);
}
if (has_folder_id) {
parse(folder_id, parser);
}
if (has_pending_read_channel_inbox) {
parse(pending_read_channel_inbox_pts, parser);
parse(pending_read_channel_inbox_max_message_id, parser);
parse(pending_read_channel_inbox_server_unread_count, parser);
}
int32 action_bar_distance = -1;
if (action_bar_has_distance) {
parse(action_bar_distance, parser);
}
if (has_active_group_call_id) {
parse(active_group_call_id, parser);
}
if (has_message_ttl) {
parse(message_ttl, parser);
}
if (has_default_join_group_call_as_dialog_id) {
parse(default_join_group_call_as_dialog_id, parser);
}
if (has_theme_name) {
parse(theme_name, parser);
}
if (has_pending_join_requests) {
parse(pending_join_request_count, parser);
parse(pending_join_request_user_ids, parser);
}
if (has_action_bar) {
parse(action_bar, parser);
}
if (has_default_send_message_as_dialog_id) {
parse(default_send_message_as_dialog_id, parser);
}
if (has_available_reactions) {
parse(available_reactions, parser);
} else if (has_legacy_available_reactions) {
vector<string> legacy_available_reactions;
parse(legacy_available_reactions, parser);
available_reactions = ChatReactions(std::move(legacy_available_reactions));
}
if (has_available_reactions_generation) {
parse(available_reactions_generation, parser);
}
if (has_have_full_history_source) {
parse(have_full_history_source, parser);
}
if (has_history_generation) {
parse(history_generation, parser);
}
(void)legacy_know_can_report_spam;
if (know_action_bar && !has_action_bar) {
action_bar = DialogActionBar::create(
action_bar_can_report_spam, action_bar_can_add_contact, action_bar_can_block_user,
action_bar_can_share_phone_number, action_bar_can_report_location, action_bar_can_unarchive,
has_outgoing_messages ? -1 : action_bar_distance, action_bar_can_invite_members, string(), false, 0);
}
}
template <class StorerT>
void MessagesManager::CallsDbState::store(StorerT &storer) const {
using td::store;
store(static_cast<int32>(first_calls_database_message_id_by_index.size()), storer);
for (auto first_message_id : first_calls_database_message_id_by_index) {
store(first_message_id, storer);
}
store(static_cast<int32>(message_count_by_index.size()), storer);
for (auto message_count : message_count_by_index) {
store(message_count, storer);
}
}
template <class ParserT>
void MessagesManager::CallsDbState::parse(ParserT &parser) {
using td::parse;
int32 size;
parse(size, parser);
LOG_CHECK(static_cast<size_t>(size) <= first_calls_database_message_id_by_index.size())
<< size << " " << first_calls_database_message_id_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(first_calls_database_message_id_by_index[i], parser);
}
parse(size, parser);
LOG_CHECK(static_cast<size_t>(size) <= message_count_by_index.size()) << size << " " << message_count_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(message_count_by_index[i], parser);
}
}
void MessagesManager::load_calls_db_state() {
if (!G()->parameters().use_message_db) {
return;
}
std::fill(calls_db_state_.message_count_by_index.begin(), calls_db_state_.message_count_by_index.end(), -1);
auto value = G()->td_db()->get_sqlite_sync_pmc()->get("calls_db_state");
if (value.empty()) {
return;
}
log_event_parse(calls_db_state_, value).ensure();
LOG(INFO) << "Save calls database state " << calls_db_state_.first_calls_database_message_id_by_index[0] << " ("
<< calls_db_state_.message_count_by_index[0] << ") "
<< calls_db_state_.first_calls_database_message_id_by_index[1] << " ("
<< calls_db_state_.message_count_by_index[1] << ")";
}
void MessagesManager::save_calls_db_state() {
if (!G()->parameters().use_message_db) {
return;
}
LOG(INFO) << "Save calls database state " << calls_db_state_.first_calls_database_message_id_by_index[0] << " ("
<< calls_db_state_.message_count_by_index[0] << ") "
<< calls_db_state_.first_calls_database_message_id_by_index[1] << " ("
<< calls_db_state_.message_count_by_index[1] << ")";
G()->td_db()->get_sqlite_pmc()->set("calls_db_state", log_event_store(calls_db_state_).as_slice().str(), Auto());
}
MessagesManager::MessagesManager(Td *td, ActorShared<> parent)
: recently_found_dialogs_{td, "recently_found", MAX_RECENT_DIALOGS}
, recently_opened_dialogs_{td, "recently_opened", MAX_RECENT_DIALOGS}
, td_(td)
, parent_(std::move(parent)) {
upload_media_callback_ = std::make_shared<UploadMediaCallback>();
upload_thumbnail_callback_ = std::make_shared<UploadThumbnailCallback>();
upload_dialog_photo_callback_ = std::make_shared<UploadDialogPhotoCallback>();
upload_imported_messages_callback_ = std::make_shared<UploadImportedMessagesCallback>();
upload_imported_message_attachment_callback_ = std::make_shared<UploadImportedMessageAttachmentCallback>();
channel_get_difference_timeout_.set_callback(on_channel_get_difference_timeout_callback);
channel_get_difference_timeout_.set_callback_data(static_cast<void *>(this));
channel_get_difference_retry_timeout_.set_callback(on_channel_get_difference_timeout_callback);
channel_get_difference_retry_timeout_.set_callback_data(static_cast<void *>(this));
pending_message_views_timeout_.set_callback(on_pending_message_views_timeout_callback);
pending_message_views_timeout_.set_callback_data(static_cast<void *>(this));
pending_message_live_location_view_timeout_.set_callback(on_pending_message_live_location_view_timeout_callback);
pending_message_live_location_view_timeout_.set_callback_data(static_cast<void *>(this));
pending_draft_message_timeout_.set_callback(on_pending_draft_message_timeout_callback);
pending_draft_message_timeout_.set_callback_data(static_cast<void *>(this));
pending_read_history_timeout_.set_callback(on_pending_read_history_timeout_callback);
pending_read_history_timeout_.set_callback_data(static_cast<void *>(this));
pending_updated_dialog_timeout_.set_callback(on_pending_updated_dialog_timeout_callback);
pending_updated_dialog_timeout_.set_callback_data(static_cast<void *>(this));
pending_unload_dialog_timeout_.set_callback(on_pending_unload_dialog_timeout_callback);
pending_unload_dialog_timeout_.set_callback_data(static_cast<void *>(this));
dialog_unmute_timeout_.set_callback(on_dialog_unmute_timeout_callback);
dialog_unmute_timeout_.set_callback_data(static_cast<void *>(this));
pending_send_dialog_action_timeout_.set_callback(on_pending_send_dialog_action_timeout_callback);
pending_send_dialog_action_timeout_.set_callback_data(static_cast<void *>(this));
active_dialog_action_timeout_.set_callback(on_active_dialog_action_timeout_callback);
active_dialog_action_timeout_.set_callback_data(static_cast<void *>(this));
update_dialog_online_member_count_timeout_.set_callback(on_update_dialog_online_member_count_timeout_callback);
update_dialog_online_member_count_timeout_.set_callback_data(static_cast<void *>(this));
preload_folder_dialog_list_timeout_.set_callback(on_preload_folder_dialog_list_timeout_callback);
preload_folder_dialog_list_timeout_.set_callback_data(static_cast<void *>(this));
update_viewed_messages_timeout_.set_callback(on_update_viewed_messages_timeout_callback);
update_viewed_messages_timeout_.set_callback_data(static_cast<void *>(this));
}
MessagesManager::~MessagesManager() {
Scheduler::instance()->destroy_on_scheduler(
G()->get_gc_scheduler_id(), ttl_nodes_, ttl_heap_, being_sent_messages_, update_message_ids_,
update_scheduled_message_ids_, message_id_to_dialog_id_, last_clear_history_message_id_to_dialog_id_, dialogs_,
postponed_chat_read_inbox_updates_, found_public_dialogs_, found_on_server_dialogs_, found_common_dialogs_,
message_embedding_codes_[0], message_embedding_codes_[1], replied_by_media_timestamp_messages_,
notification_group_id_to_dialog_id_, active_get_channel_differencies_, get_channel_difference_to_log_event_id_,
channel_get_difference_retry_timeouts_, is_channel_difference_finished_, resolved_usernames_,
inaccessible_resolved_usernames_, dialog_bot_command_message_ids_, full_message_id_to_file_source_id_,
last_outgoing_forwarded_message_date_, dialog_viewed_messages_, dialog_online_member_counts_,
previous_repaired_read_inbox_max_message_id_, failed_to_load_dialogs_);
}
void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_channel_get_difference_timeout,
DialogId(dialog_id_int));
}
void MessagesManager::on_pending_message_views_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_pending_message_views_timeout,
DialogId(dialog_id_int));
}
void MessagesManager::on_pending_message_live_location_view_timeout_callback(void *messages_manager_ptr,
int64 task_id) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager),
&MessagesManager::view_message_live_location_on_server, task_id);
}
void MessagesManager::on_pending_draft_message_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager),
&MessagesManager::save_dialog_draft_message_on_server, DialogId(dialog_id_int));
}
void MessagesManager::on_pending_read_history_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::do_read_history_on_server,
DialogId(dialog_id_int));
}
void MessagesManager::on_pending_updated_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
// no check for G()->close_flag() to save dialogs even while closing
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
// TODO it is unsafe to save dialog to database before binlog is flushed
// no send_closure_later, because messages_manager can be not an actor while closing
messages_manager->save_dialog_to_database(DialogId(dialog_id_int));
}
void MessagesManager::on_pending_unload_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::unload_dialog,
DialogId(dialog_id_int));
}
void MessagesManager::on_dialog_unmute_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_dialog_unmute,
DialogId(dialog_id_int));
}
void MessagesManager::on_pending_send_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_send_dialog_action_timeout,
DialogId(dialog_id_int));
}
void MessagesManager::on_active_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_active_dialog_action_timeout,
DialogId(dialog_id_int));
}
void MessagesManager::on_update_dialog_online_member_count_timeout_callback(void *messages_manager_ptr,
int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager),
&MessagesManager::on_update_dialog_online_member_count_timeout, DialogId(dialog_id_int));
}
void MessagesManager::on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::preload_folder_dialog_list,
FolderId(narrow_cast<int32>(folder_id_int)));
}
void MessagesManager::on_update_viewed_messages_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_update_viewed_messages_timeout,
DialogId(dialog_id_int));
}
BufferSlice MessagesManager::get_dialog_database_value(const Dialog *d) {
// can't use log_event_store, because it tries to parse stored Dialog
LogEventStorerCalcLength storer_calc_length;
store(*d, storer_calc_length);
BufferSlice value_buffer{storer_calc_length.get_length()};
auto value = value_buffer.as_slice();
LogEventStorerUnsafe storer_unsafe(value.ubegin());
store(*d, storer_unsafe);
return value_buffer;
}
void MessagesManager::save_dialog_to_database(DialogId dialog_id) {
CHECK(G()->parameters().use_message_db);
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
LOG(INFO) << "Save " << dialog_id << " to database";
vector<NotificationGroupKey> changed_group_keys;
bool can_reuse_notification_group = false;
auto add_group_key = [&](auto &group_info) {
if (group_info.is_changed) {
can_reuse_notification_group |= group_info.try_reuse;
changed_group_keys.emplace_back(group_info.group_id, group_info.try_reuse ? DialogId() : dialog_id,
group_info.last_notification_date);
group_info.is_changed = false;
}
};
add_group_key(d->message_notification_group);
add_group_key(d->mention_notification_group);
auto fixed_folder_id = d->folder_id == FolderId::archive() ? FolderId::archive() : FolderId::main();
G()->td_db()->get_dialog_db_async()->add_dialog(
dialog_id, fixed_folder_id, d->is_folder_id_inited ? d->order : 0, get_dialog_database_value(d),
std::move(changed_group_keys), PromiseCreator::lambda([dialog_id, can_reuse_notification_group](Result<> result) {
send_closure(G()->messages_manager(), &MessagesManager::on_save_dialog_to_database, dialog_id,
can_reuse_notification_group, result.is_ok());
}));
}
void MessagesManager::on_save_dialog_to_database(DialogId dialog_id, bool can_reuse_notification_group, bool success) {
LOG(INFO) << "Successfully saved " << dialog_id << " to database";
if (success && can_reuse_notification_group && !G()->close_flag()) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
try_reuse_notification_group(d->message_notification_group);
try_reuse_notification_group(d->mention_notification_group);
}
// TODO erase some events from binlog
}
void MessagesManager::try_reuse_notification_group(NotificationGroupInfo &group_info) {
if (!group_info.try_reuse) {
return;
}
if (group_info.is_changed) {
LOG(ERROR) << "Failed to reuse changed " << group_info.group_id;
return;
}
group_info.try_reuse = false;
if (!group_info.group_id.is_valid()) {
LOG(ERROR) << "Failed to reuse invalid " << group_info.group_id;
return;
}
CHECK(group_info.last_notification_id == NotificationId());
CHECK(group_info.last_notification_date == 0);
send_closure_later(G()->notification_manager(), &NotificationManager::try_reuse_notification_group_id,
group_info.group_id);
notification_group_id_to_dialog_id_.erase(group_info.group_id);
group_info.group_id = NotificationGroupId();
group_info.max_removed_notification_id = NotificationId();
group_info.max_removed_message_id = MessageId();
}
void MessagesManager::invalidate_message_indexes(Dialog *d) {
CHECK(d != nullptr);
bool is_secret = d->dialog_id.get_type() == DialogType::SecretChat;
for (size_t i = 0; i < d->message_count_by_index.size(); i++) {
if (is_secret || i == static_cast<size_t>(message_search_filter_index(MessageSearchFilter::FailedToSend))) {
// always know all messages
d->first_database_message_id_by_index[i] = MessageId::min();
// keep the count
} else {
// some messages are unknown; drop first_database_message_id and count
d->first_database_message_id_by_index[i] = MessageId();
d->message_count_by_index[i] = -1;
}
}
}
void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const Message *m) {
auto index_mask = get_message_index_mask(d->dialog_id, m);
index_mask &= ~message_search_filter_index_mask(
MessageSearchFilter::UnreadMention); // unread mention count has been already manually updated
index_mask &= ~message_search_filter_index_mask(
MessageSearchFilter::UnreadReaction); // unread reaction count has been already manually updated
update_message_count_by_index(d, diff, index_mask);
}
void MessagesManager::update_message_count_by_index(Dialog *d, int diff, int32 index_mask) {
if (index_mask == 0) {
return;
}
LOG(INFO) << "Update message count by " << diff << " in index mask " << index_mask;
int i = 0;
for (auto &message_count : d->message_count_by_index) {
if (((index_mask >> i) & 1) != 0 && message_count != -1) {
message_count += diff;
if (message_count < 0) {
if (d->dialog_id.get_type() == DialogType::SecretChat ||
i == message_search_filter_index(MessageSearchFilter::FailedToSend)) {
message_count = 0;
} else {
message_count = -1;
}
}
on_dialog_updated(d->dialog_id, "update_message_count_by_index");
}
i++;
}
i = static_cast<int>(MessageSearchFilter::Call) - 1;
for (auto &message_count : calls_db_state_.message_count_by_index) {
if (((index_mask >> i) & 1) != 0 && message_count != -1) {
message_count += diff;
if (message_count < 0) {
if (d->dialog_id.get_type() == DialogType::SecretChat) {
message_count = 0;
} else {
message_count = -1;
}
}
save_calls_db_state();
}
i++;
}
}
int32 MessagesManager::get_message_index_mask(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
if (m->message_id.is_scheduled() || m->message_id.is_yet_unsent()) {
return 0;
}
if (m->is_failed_to_send) {
return message_search_filter_index_mask(MessageSearchFilter::FailedToSend);
}
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
if (!m->message_id.is_server() && !is_secret) {
return 0;
}
int32 index_mask = 0;
if (m->is_pinned) {
index_mask |= message_search_filter_index_mask(MessageSearchFilter::Pinned);
}
// retain second condition just in case
if (m->is_content_secret || (m->ttl > 0 && !is_secret)) {
return index_mask;
}
index_mask |= get_message_content_index_mask(m->content.get(), td_, m->is_outgoing);
if (m->contains_mention) {
index_mask |= message_search_filter_index_mask(MessageSearchFilter::Mention);
if (m->contains_unread_mention) {
index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadMention);
}
}
if (has_unread_message_reactions(dialog_id, m)) {
index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadReaction);
}
LOG(INFO) << "Have index mask " << index_mask << " for " << m->message_id << " in " << dialog_id;
return index_mask;
}
void MessagesManager::update_reply_count_by_message(Dialog *d, int diff, const Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || !m->top_thread_message_id.is_valid() ||
m->top_thread_message_id == m->message_id || !m->message_id.is_valid() || !m->message_id.is_server()) {
return;
}
update_message_reply_count(d, m->top_thread_message_id, get_message_sender(m), m->message_id,
diff < 0 ? G()->unix_time() : m->date, diff);
}
void MessagesManager::update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id,
MessageId reply_message_id, int32 update_date, int diff,
bool is_recursive) {
if (d == nullptr) {
return;
}
Message *m = get_message(d, message_id);
if (m == nullptr || !is_active_message_reply_info(d->dialog_id, m->reply_info)) {
return;
}
LOG(INFO) << "Update reply count to " << message_id << " in " << d->dialog_id << " by " << diff << " from "
<< reply_message_id << " sent by " << replier_dialog_id;
if (m->interaction_info_update_date < update_date &&
m->reply_info.add_reply(replier_dialog_id, reply_message_id, diff)) {
on_message_reply_info_changed(d->dialog_id, m);
on_message_changed(d, m, true, "update_message_reply_count_by_message");
}
if (!is_recursive && is_discussion_message(d->dialog_id, m)) {
update_message_reply_count(get_dialog(m->forward_info->from_dialog_id), m->forward_info->from_message_id,
replier_dialog_id, reply_message_id, update_date, diff, true);
}
}
tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_input_peer(DialogId dialog_id,
AccessRights access_rights) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return td_->contacts_manager_->get_input_peer_user(dialog_id.get_user_id(), access_rights);
case DialogType::Chat:
return td_->contacts_manager_->get_input_peer_chat(dialog_id.get_chat_id(), access_rights);
case DialogType::Channel:
return td_->contacts_manager_->get_input_peer_channel(dialog_id.get_channel_id(), access_rights);
case DialogType::SecretChat:
return nullptr;
case DialogType::None:
return make_tl_object<telegram_api::inputPeerEmpty>();
default:
UNREACHABLE();
return nullptr;
}
}
tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_input_peer_force(DialogId dialog_id) {
switch (dialog_id.get_type()) {
case DialogType::User: {
UserId user_id = dialog_id.get_user_id();
return make_tl_object<telegram_api::inputPeerUser>(user_id.get(), 0);
}
case DialogType::Chat: {
ChatId chat_id = dialog_id.get_chat_id();
return make_tl_object<telegram_api::inputPeerChat>(chat_id.get());
}
case DialogType::Channel: {
ChannelId channel_id = dialog_id.get_channel_id();
return make_tl_object<telegram_api::inputPeerChannel>(channel_id.get(), 0);
}
case DialogType::SecretChat:
case DialogType::None:
return make_tl_object<telegram_api::inputPeerEmpty>();
default:
UNREACHABLE();
return nullptr;
}
}
vector<tl_object_ptr<telegram_api::InputPeer>> MessagesManager::get_input_peers(const vector<DialogId> &dialog_ids,
AccessRights access_rights) const {
vector<tl_object_ptr<telegram_api::InputPeer>> input_peers;
input_peers.reserve(dialog_ids.size());
for (auto &dialog_id : dialog_ids) {
auto input_peer = get_input_peer(dialog_id, access_rights);
if (input_peer == nullptr) {
LOG(ERROR) << "Have no access to " << dialog_id;
continue;
}
input_peers.push_back(std::move(input_peer));
}
return input_peers;
}
tl_object_ptr<telegram_api::InputDialogPeer> MessagesManager::get_input_dialog_peer(DialogId dialog_id,
AccessRights access_rights) const {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
case DialogType::None:
return make_tl_object<telegram_api::inputDialogPeer>(get_input_peer(dialog_id, access_rights));
case DialogType::SecretChat:
return nullptr;
default:
UNREACHABLE();
return nullptr;
}
}
vector<tl_object_ptr<telegram_api::InputDialogPeer>> MessagesManager::get_input_dialog_peers(
const vector<DialogId> &dialog_ids, AccessRights access_rights) const {
vector<tl_object_ptr<telegram_api::InputDialogPeer>> input_dialog_peers;
input_dialog_peers.reserve(dialog_ids.size());
for (auto &dialog_id : dialog_ids) {
auto input_dialog_peer = get_input_dialog_peer(dialog_id, access_rights);
if (input_dialog_peer == nullptr) {
LOG(ERROR) << "Have no access to " << dialog_id;
continue;
}
input_dialog_peers.push_back(std::move(input_dialog_peer));
}
return input_dialog_peers;
}
bool MessagesManager::have_input_peer(DialogId dialog_id, AccessRights access_rights) const {
switch (dialog_id.get_type()) {
case DialogType::User: {
UserId user_id = dialog_id.get_user_id();
return td_->contacts_manager_->have_input_peer_user(user_id, access_rights);
}
case DialogType::Chat: {
ChatId chat_id = dialog_id.get_chat_id();
return td_->contacts_manager_->have_input_peer_chat(chat_id, access_rights);
}
case DialogType::Channel: {
ChannelId channel_id = dialog_id.get_channel_id();
return td_->contacts_manager_->have_input_peer_channel(channel_id, access_rights);
}
case DialogType::SecretChat: {
SecretChatId secret_chat_id = dialog_id.get_secret_chat_id();
return td_->contacts_manager_->have_input_encrypted_peer(secret_chat_id, access_rights);
}
case DialogType::None:
return false;
default:
UNREACHABLE();
return false;
}
}
bool MessagesManager::have_dialog_info(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User: {
UserId user_id = dialog_id.get_user_id();
return td_->contacts_manager_->have_user(user_id);
}
case DialogType::Chat: {
ChatId chat_id = dialog_id.get_chat_id();
return td_->contacts_manager_->have_chat(chat_id);
}
case DialogType::Channel: {
ChannelId channel_id = dialog_id.get_channel_id();
return td_->contacts_manager_->have_channel(channel_id);
}
case DialogType::SecretChat: {
SecretChatId secret_chat_id = dialog_id.get_secret_chat_id();
return td_->contacts_manager_->have_secret_chat(secret_chat_id);
}
case DialogType::None:
default:
return false;
}
}
bool MessagesManager::have_dialog_info_force(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User: {
UserId user_id = dialog_id.get_user_id();
return td_->contacts_manager_->have_user_force(user_id);
}
case DialogType::Chat: {
ChatId chat_id = dialog_id.get_chat_id();
return td_->contacts_manager_->have_chat_force(chat_id);
}
case DialogType::Channel: {
ChannelId channel_id = dialog_id.get_channel_id();
return td_->contacts_manager_->have_channel_force(channel_id);
}
case DialogType::SecretChat: {
SecretChatId secret_chat_id = dialog_id.get_secret_chat_id();
return td_->contacts_manager_->have_secret_chat_force(secret_chat_id);
}
case DialogType::None:
default:
return false;
}
}
tl_object_ptr<telegram_api::inputEncryptedChat> MessagesManager::get_input_encrypted_chat(
DialogId dialog_id, AccessRights access_rights) const {
switch (dialog_id.get_type()) {
case DialogType::SecretChat: {
SecretChatId secret_chat_id = dialog_id.get_secret_chat_id();
return td_->contacts_manager_->get_input_encrypted_chat(secret_chat_id, access_rights);
}
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
case DialogType::None:
default:
UNREACHABLE();
return nullptr;
}
}
bool MessagesManager::is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update) {
auto constructor_id = update->get_id();
if (constructor_id == dummyUpdate::ID) {
// allow dummyUpdate just in case
return true;
}
if (constructor_id == telegram_api::updateNewMessage::ID ||
constructor_id == telegram_api::updateNewChannelMessage::ID) {
// new outgoing messages are received again if random_id coincide
return true;
}
return false;
}
void MessagesManager::skip_old_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
int32 old_pts, int32 pts_count, const char *source) {
if (update->get_id() == telegram_api::updateNewMessage::ID) {
auto update_new_message = static_cast<telegram_api::updateNewMessage *>(update.get());
auto full_message_id = FullMessageId::get_full_message_id(update_new_message->message_, false);
if (update_message_ids_.count(full_message_id) > 0) {
if (new_pts == old_pts || old_pts == std::numeric_limits<int32>::max()) {
// apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow
on_get_message(std::move(update_new_message->message_), true, false, false, true, true,
"updateNewMessage with an awaited message");
return;
} else {
LOG(ERROR) << "Receive awaited sent " << full_message_id << " from " << source << " with pts " << new_pts
<< " and pts_count " << pts_count << ", but current pts is " << old_pts;
dump_debug_message_op(get_dialog(full_message_id.get_dialog_id()), 3);
}
}
}
if (update->get_id() == updateSentMessage::ID) {
auto update_sent_message = static_cast<updateSentMessage *>(update.get());
if (being_sent_messages_.count(update_sent_message->random_id_) > 0) {
if (new_pts == old_pts || old_pts == std::numeric_limits<int32>::max()) {
// apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow
on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_,
update_sent_message->date_, update_sent_message->ttl_period_, FileId(),
"process old updateSentMessage");
return;
} else if (update_sent_message->random_id_ != 0) {
LOG(ERROR) << "Receive awaited sent " << update_sent_message->message_id_ << " from " << source << " with pts "
<< new_pts << " and pts_count " << pts_count << ", but current pts is " << old_pts;
dump_debug_message_op(get_dialog(being_sent_messages_[update_sent_message->random_id_].get_dialog_id()), 3);
}
}
return;
}
// very old or unuseful update
LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update))
<< "Receive useless update " << oneline(to_string(update)) << " from " << source;
}
MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() {
UserId service_notifications_user_id = td_->contacts_manager_->add_service_notifications_user();
DialogId service_notifications_dialog_id(service_notifications_user_id);
force_create_dialog(service_notifications_dialog_id, "get_service_notifications_dialog");
return get_dialog(service_notifications_dialog_id);
}
void MessagesManager::save_auth_notification_ids() {
auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME;
vector<string> ids;
for (const auto &it : auth_notification_id_date_) {
auto date = it.second;
if (date < min_date) {
continue;
}
ids.push_back(it.first);
ids.push_back(to_string(date));
}
if (ids.empty()) {
G()->td_db()->get_binlog_pmc()->erase("auth_notification_ids");
return;
}
G()->td_db()->get_binlog_pmc()->set("auth_notification_ids", implode(ids, ','));
}
void MessagesManager::on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update,
bool skip_new_entities, Promise<Unit> &&promise) {
if (td_->auth_manager_->is_bot()) {
return;
}
bool has_date = (update->flags_ & telegram_api::updateServiceNotification::INBOX_DATE_MASK) != 0;
auto date = has_date ? update->inbox_date_ : G()->unix_time();
if (date <= 0) {
LOG(ERROR) << "Receive message date " << date << " in " << to_string(update);
return;
}
bool is_auth_notification = update->type_.size() >= 5 && begins_with(update->type_, "auth");
if (is_auth_notification) {
auto &old_date = auth_notification_id_date_[update->type_.substr(4)];
if (date <= old_date) {
LOG(INFO) << "Skip already applied " << to_string(update);
return;
}
old_date = date;
if (auth_notification_id_date_.size() > MAX_SAVED_AUTH_NOTIFICATION_IDS) {
auto min_date = date + 1;
const string *min_key = nullptr;
for (const auto &it : auth_notification_id_date_) {
if (it.second < min_date) {
min_date = it.second;
min_key = &it.first;
}
}
CHECK(min_key != nullptr);
auth_notification_id_date_.erase(*min_key);
}
}
bool is_authorized = td_->auth_manager_->is_authorized();
bool is_user = is_authorized && !td_->auth_manager_->is_bot();
auto contacts_manager = is_authorized ? td_->contacts_manager_.get() : nullptr;
auto message_text = get_message_text(contacts_manager, std::move(update->message_), std::move(update->entities_),
skip_new_entities, !is_user, date, false, "on_update_service_notification");
DialogId owner_dialog_id = is_user ? get_service_notifications_dialog()->dialog_id : DialogId();
int32 ttl = 0;
bool disable_web_page_preview = false;
auto content = get_message_content(td_, std::move(message_text), std::move(update->media_), owner_dialog_id, false,
UserId(), &ttl, &disable_web_page_preview, "updateServiceNotification");
bool is_content_secret = is_secret_message_content(ttl, content->get_type());
if (update->popup_) {
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, -1)));
}
if (has_date && is_user) {
Dialog *d = get_service_notifications_dialog();
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
CHECK(dialog_id.get_type() == DialogType::User);
auto new_message = make_unique<Message>();
set_message_id(new_message, get_next_local_message_id(d));
new_message->sender_user_id = dialog_id.get_user_id();
new_message->date = date;
new_message->ttl = ttl;
new_message->disable_web_page_preview = disable_web_page_preview;
new_message->is_content_secret = is_content_secret;
new_message->content = std::move(content);
new_message->have_previous = true;
new_message->have_next = true;
bool need_update = true;
bool need_update_dialog_pos = false;
const Message *m = add_message_to_dialog(d, std::move(new_message), true, &need_update, &need_update_dialog_pos,
"on_update_service_notification");
if (m != nullptr && need_update) {
send_update_new_message(d, m);
}
register_new_local_message_id(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "on_update_service_notification");
}
}
promise.set_value(Unit());
if (is_auth_notification) {
save_auth_notification_ids();
}
}
void MessagesManager::on_update_read_channel_inbox(tl_object_ptr<telegram_api::updateReadChannelInbox> &&update) {
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id << " in updateReadChannelInbox";
return;
}
FolderId folder_id;
if ((update->flags_ & telegram_api::updateReadChannelInbox::FOLDER_ID_MASK) != 0) {
folder_id = FolderId(update->folder_id_);
}
on_update_dialog_folder_id(DialogId(channel_id), folder_id);
on_read_channel_inbox(channel_id, MessageId(ServerMessageId(update->max_id_)), update->still_unread_count_,
update->pts_, "updateReadChannelInbox");
}
void MessagesManager::on_update_read_channel_outbox(tl_object_ptr<telegram_api::updateReadChannelOutbox> &&update) {
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id << " in updateReadChannelOutbox";
return;
}
DialogId dialog_id = DialogId(channel_id);
read_history_outbox(dialog_id, MessageId(ServerMessageId(update->max_id_)));
}
void MessagesManager::on_update_read_channel_messages_contents(
tl_object_ptr<telegram_api::updateChannelReadMessagesContents> &&update) {
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id << " in updateChannelReadMessagesContents";
return;
}
if (td_->auth_manager_->is_bot()) {
return;
}
DialogId dialog_id = DialogId(channel_id);
Dialog *d = get_dialog_force(dialog_id, "on_update_read_channel_messages_contents");
if (d == nullptr) {
LOG(INFO) << "Receive read channel messages contents update in unknown " << dialog_id;
return;
}
if ((update->flags_ & telegram_api::updateChannelReadMessagesContents::TOP_MSG_ID_MASK) != 0) {
// TODO
return;
}
for (auto &server_message_id : update->messages_) {
read_channel_message_content_from_updates(d, MessageId(ServerMessageId(server_message_id)));
}
}
void MessagesManager::on_update_read_message_comments(DialogId dialog_id, MessageId message_id,
MessageId max_message_id, MessageId last_read_inbox_message_id,
MessageId last_read_outbox_message_id, int32 unread_count) {
Dialog *d = get_dialog_force(dialog_id, "on_update_read_message_comments");
if (d == nullptr) {
LOG(INFO) << "Ignore update of read message comments in unknown " << dialog_id << " in updateReadDiscussion";
return;
}
auto m = get_message_force(d, message_id, "on_update_read_message_comments");
if (m == nullptr || !m->message_id.is_server() || m->top_thread_message_id != m->message_id) {
return;
}
if (m->is_topic_message) {
td_->forum_topic_manager_->on_update_forum_topic_unread(
dialog_id, message_id, max_message_id, last_read_inbox_message_id, last_read_outbox_message_id, unread_count);
}
if (!is_active_message_reply_info(dialog_id, m->reply_info)) {
return;
}
if (m->reply_info.update_max_message_ids(max_message_id, last_read_inbox_message_id, last_read_outbox_message_id)) {
on_message_reply_info_changed(dialog_id, m);
on_message_changed(d, m, true, "on_update_read_message_comments");
}
}
void MessagesManager::on_update_channel_too_long(tl_object_ptr<telegram_api::updateChannelTooLong> &&update,
bool force_apply) {
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id << " in updateChannelTooLong";
return;
}
DialogId dialog_id = DialogId(channel_id);
auto d = get_dialog_force(dialog_id, "on_update_channel_too_long 4");
if (d == nullptr) {
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
d = add_dialog(dialog_id, "on_update_channel_too_long 5");
CHECK(d != nullptr);
CHECK(d->pts == pts);
update_dialog_pos(d, "on_update_channel_too_long 6");
}
}
if (d != nullptr) {
if (update->pts_ == 0 || update->pts_ > d->pts) {
get_channel_difference(dialog_id, d->pts, true, "on_update_channel_too_long 1");
}
} else {
if (force_apply) {
get_channel_difference(dialog_id, -1, true, "on_update_channel_too_long 2");
} else {
td_->updates_manager_->schedule_get_difference("on_update_channel_too_long 3");
}
}
}
void MessagesManager::on_update_message_view_count(FullMessageId full_message_id, int32 view_count) {
if (view_count < 0) {
LOG(ERROR) << "Receive " << view_count << " views in updateChannelMessageViews for " << full_message_id;
return;
}
update_message_interaction_info(full_message_id, view_count, -1, false, nullptr, false, nullptr);
}
void MessagesManager::on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count) {
if (forward_count < 0) {
LOG(ERROR) << "Receive " << forward_count << " forwards in updateChannelMessageForwards for " << full_message_id;
return;
}
update_message_interaction_info(full_message_id, -1, forward_count, false, nullptr, false, nullptr);
}
void MessagesManager::on_update_message_reactions(FullMessageId full_message_id,
tl_object_ptr<telegram_api::messageReactions> &&reactions,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
auto new_reactions = MessageReactions::get_message_reactions(td_, std::move(reactions), td_->auth_manager_->is_bot());
if (!have_message_force(full_message_id, "on_update_message_reactions")) {
auto dialog_id = full_message_id.get_dialog_id();
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(INFO) << "Ignore updateMessageReaction in inaccessible " << full_message_id;
promise.set_value(Unit());
return;
}
const Dialog *d = get_dialog(dialog_id);
if (d == nullptr) {
LOG(INFO) << "Ignore updateMessageReaction in unknown " << dialog_id;
promise.set_value(Unit());
return;
}
// there is no message, so the update can be ignored
if ((new_reactions != nullptr && !new_reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0) {
// but if there are unread reactions or the chat has unread reactions,
// then number of unread reactions could have been changed, so reload the number of unread reactions
send_get_dialog_query(dialog_id, std::move(promise), 0, "on_update_message_reactions");
} else {
promise.set_value(Unit());
}
return;
}
update_message_interaction_info(full_message_id, -1, -1, false, nullptr, true, std::move(new_reactions));
promise.set_value(Unit());
}
void MessagesManager::update_message_reactions(FullMessageId full_message_id,
unique_ptr<MessageReactions> &&reactions) {
update_message_interaction_info(full_message_id, -1, -1, false, nullptr, true, std::move(reactions));
}
void MessagesManager::on_get_message_reaction_list(FullMessageId full_message_id, const string &reaction,
FlatHashMap<string, vector<DialogId>> reactions, int32 total_count) {
const Message *m = get_message_force(full_message_id, "on_get_message_reaction_list");
if (m == nullptr || m->reactions == nullptr) {
return;
}
// it's impossible to use received reactions to update message reactions, because there is no way to find,
// which reactions are chosen by the current user, so just reload message reactions for consistency
if (m->reactions->are_consistent_with_list(reaction, std::move(reactions), total_count)) {
return;
}
LOG(INFO) << "Need reload reactions in " << full_message_id << " for consistency";
auto it = pending_reactions_.find(full_message_id);
if (it != pending_reactions_.end()) {
it->second.was_updated = true;
} else {
queue_message_reactions_reload(full_message_id);
}
}
void MessagesManager::on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count,
int32 forward_count, bool has_reply_info,
tl_object_ptr<telegram_api::messageReplies> &&reply_info) {
if (view_count < 0 || forward_count < 0) {
LOG(ERROR) << "Receive " << view_count << "/" << forward_count << " interaction counters for " << full_message_id;
return;
}
update_message_interaction_info(full_message_id, view_count, forward_count, has_reply_info, std::move(reply_info),
false, nullptr);
}
void MessagesManager::on_pending_message_views_timeout(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
const size_t MAX_MESSAGE_VIEWS = 100; // server side limit
vector<MessageId> message_ids;
message_ids.reserve(min(d->pending_viewed_message_ids.size(), MAX_MESSAGE_VIEWS));
for (auto message_id : d->pending_viewed_message_ids) {
auto *m = get_message(d, message_id);
if (m == nullptr) {
continue;
}
if (m->has_get_message_views_query) {
if (!d->increment_view_counter || m->need_view_counter_increment) {
continue;
}
m->need_view_counter_increment = true;
} else {
m->has_get_message_views_query = true;
m->need_view_counter_increment = d->increment_view_counter;
}
message_ids.push_back(message_id);
if (message_ids.size() >= MAX_MESSAGE_VIEWS) {
td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids), d->increment_view_counter);
message_ids.clear();
}
}
if (!message_ids.empty()) {
td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids), d->increment_view_counter);
}
d->pending_viewed_message_ids.clear();
d->increment_view_counter = false;
}
void MessagesManager::update_message_interaction_info(FullMessageId full_message_id, int32 view_count,
int32 forward_count, bool has_reply_info,
tl_object_ptr<telegram_api::messageReplies> &&reply_info,
bool has_reactions, unique_ptr<MessageReactions> &&reactions) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "update_message_interaction_info");
if (d == nullptr) {
return;
}
auto message_id = full_message_id.get_message_id();
Message *m = get_message_force(d, message_id, "update_message_interaction_info");
if (m == nullptr) {
LOG(INFO) << "Ignore message interaction info about unknown " << full_message_id;
if (!message_id.is_scheduled() && message_id > d->last_new_message_id && d->last_new_message_id.is_valid() &&
dialog_id.get_type() == DialogType::Channel) {
get_channel_difference(dialog_id, d->pts, true, "update_message_interaction_info");
}
return;
}
if (view_count < 0) {
view_count = m->view_count;
}
if (forward_count < 0) {
forward_count = m->forward_count;
}
bool is_empty_reply_info = reply_info == nullptr;
MessageReplyInfo new_reply_info(td_, std::move(reply_info), td_->auth_manager_->is_bot());
if (new_reply_info.is_empty() && !is_empty_reply_info) {
has_reply_info = false;
}
if (update_message_interaction_info(d, m, view_count, forward_count, has_reply_info, std::move(new_reply_info),
has_reactions, std::move(reactions), "update_message_interaction_info")) {
on_message_changed(d, m, true, "update_message_interaction_info");
}
}
bool MessagesManager::is_thread_message(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
return is_thread_message(dialog_id, m->message_id, m->reply_info, m->content->get_type());
}
bool MessagesManager::is_thread_message(DialogId dialog_id, MessageId message_id, const MessageReplyInfo &reply_info,
MessageContentType content_type) const {
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
return false;
}
if (!message_id.is_valid() || !message_id.is_server()) {
return false;
}
return !reply_info.is_empty() || reply_info.was_dropped() || content_type == MessageContentType::TopicCreate;
}
bool MessagesManager::is_active_message_reply_info(DialogId dialog_id, const MessageReplyInfo &reply_info) const {
if (reply_info.is_empty()) {
return false;
}
if (dialog_id.get_type() != DialogType::Channel) {
return false;
}
if (!reply_info.is_comment_) {
return true;
}
if (!is_broadcast_channel(dialog_id)) {
return true;
}
auto channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) {
return false;
}
auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id);
if (!linked_channel_id.is_valid()) {
// keep the comment button while linked channel is unknown
send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, Promise<Unit>(),
"is_active_message_reply_info");
return true;
}
if (linked_channel_id != reply_info.channel_id_) {
return false;
}
return true;
}
bool MessagesManager::is_visible_message_reply_info(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
if (!m->message_id.is_valid()) {
return false;
}
bool is_broadcast = is_broadcast_channel(dialog_id);
if (!m->message_id.is_server() && !(is_broadcast && m->message_id.is_yet_unsent())) {
return false;
}
if (is_broadcast && (m->had_reply_markup || m->reply_markup != nullptr)) {
return false;
}
if (!is_active_message_reply_info(dialog_id, m->reply_info)) {
return false;
}
if (m->reply_info.is_comment_ && is_broadcast &&
td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_) &&
!td_->contacts_manager_->have_input_peer_channel(m->reply_info.channel_id_, AccessRights::Read)) {
// keep the comment button while have no information about the linked channel
return false;
}
return true;
}
bool MessagesManager::is_visible_message_reactions(DialogId dialog_id, const Message *m) const {
if (m == nullptr || !m->message_id.is_valid()) {
return false;
}
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (get_message_active_reactions(d, m).empty()) {
return false;
}
if (m->available_reactions_generation != d->available_reactions_generation) {
return false;
}
return true;
}
bool MessagesManager::has_unread_message_reactions(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
return m->reactions != nullptr && !m->reactions->unread_reactions_.empty() &&
is_visible_message_reactions(dialog_id, m);
}
void MessagesManager::on_message_reply_info_changed(DialogId dialog_id, const Message *m) const {
if (td_->auth_manager_->is_bot()) {
return;
}
if (is_visible_message_reply_info(dialog_id, m)) {
send_update_message_interaction_info(dialog_id, m);
}
}
td_api::object_ptr<td_api::messageInteractionInfo> MessagesManager::get_message_interaction_info_object(
DialogId dialog_id, const Message *m) const {
bool is_visible_reply_info = is_visible_message_reply_info(dialog_id, m);
bool has_reactions =
m->reactions != nullptr && !m->reactions->reactions_.empty() && is_visible_message_reactions(dialog_id, m);
if (m->view_count == 0 && m->forward_count == 0 && !is_visible_reply_info && !has_reactions) {
return nullptr;
}
if (m->message_id.is_scheduled() && (m->forward_info == nullptr || is_broadcast_channel(dialog_id))) {
return nullptr;
}
if (m->message_id.is_local() && m->forward_info == nullptr) {
return nullptr;
}
td_api::object_ptr<td_api::messageReplyInfo> reply_info;
if (is_visible_reply_info) {
auto expected_dialog_id = m->reply_info.is_comment_ ? DialogId(m->reply_info.channel_id_) : dialog_id;
const Dialog *d = get_dialog(expected_dialog_id);
reply_info =
m->reply_info.get_message_reply_info_object(td_, d != nullptr ? d->last_read_inbox_message_id : MessageId());
CHECK(reply_info != nullptr);
}
vector<td_api::object_ptr<td_api::messageReaction>> reactions;
if (has_reactions) {
UserId my_user_id;
UserId peer_user_id;
if (dialog_id.get_type() == DialogType::User) {
my_user_id = td_->contacts_manager_->get_my_id();
peer_user_id = dialog_id.get_user_id();
}
reactions = m->reactions->get_message_reactions_object(td_, my_user_id, peer_user_id);
}
return td_api::make_object<td_api::messageInteractionInfo>(m->view_count, m->forward_count, std::move(reply_info),
std::move(reactions));
}
vector<td_api::object_ptr<td_api::unreadReaction>> MessagesManager::get_unread_reactions_object(
DialogId dialog_id, const Message *m) const {
if (!has_unread_message_reactions(dialog_id, m)) {
return {};
}
vector<td_api::object_ptr<td_api::unreadReaction>> unread_reactions;
for (const auto &unread_reaction : m->reactions->unread_reactions_) {
auto unread_reaction_object = unread_reaction.get_unread_reaction_object(td_);
if (unread_reaction_object != nullptr) {
unread_reactions.push_back(std::move(unread_reaction_object));
}
}
return unread_reactions;
}
bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int32 view_count, int32 forward_count,
bool has_reply_info, MessageReplyInfo &&reply_info,
bool has_reactions, unique_ptr<MessageReactions> &&reactions,
const char *source) {
if (td_->auth_manager_->is_bot()) {
return false;
}
CHECK(d != nullptr);
CHECK(m != nullptr);
auto dialog_id = d->dialog_id;
m->interaction_info_update_date = G()->unix_time(); // doesn't force message saving
if (m->message_id.is_valid_scheduled()) {
has_reply_info = false;
has_reactions = false;
}
bool need_update_reply_info = has_reply_info && m->reply_info.need_update_to(reply_info);
if (has_reply_info && m->reply_info.channel_id_ == reply_info.channel_id_) {
if (need_update_reply_info) {
reply_info.update_max_message_ids(m->reply_info);
} else {
if (m->reply_info.update_max_message_ids(reply_info) && view_count <= m->view_count &&
forward_count <= m->forward_count) {
on_message_reply_info_changed(dialog_id, m);
on_message_changed(d, m, true, "update_message_interaction_info");
}
}
}
if (has_reactions) {
auto it = pending_reactions_.find({dialog_id, m->message_id});
if (it != pending_reactions_.end()) {
has_reactions = false;
it->second.was_updated = true;
}
}
if (has_reactions && reactions != nullptr) {
if (m->reactions != nullptr) {
reactions->update_from(*m->reactions);
}
reactions->sort_reactions(active_reaction_pos_);
reactions->fix_chosen_reaction(get_my_dialog_id());
if (d->default_send_message_as_dialog_id.is_valid()) {
// the reaction could be set by previous owner of the broadcast
// reactions->fix_chosen_reaction(d->default_send_message_as_dialog_id);
}
}
bool need_update_reactions =
has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get());
bool need_update_unread_reactions =
has_reactions && MessageReactions::need_update_unread_reactions(m->reactions.get(), reactions.get());
bool need_update_chosen_reaction_order = has_reactions && reactions != nullptr && m->reactions != nullptr &&
m->reactions->chosen_reaction_order_ != reactions->chosen_reaction_order_;
if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info ||
need_update_reactions || need_update_unread_reactions || need_update_chosen_reaction_order) {
LOG(DEBUG) << "Update interaction info of " << FullMessageId{dialog_id, m->message_id} << " from " << m->view_count
<< '/' << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/'
<< forward_count << '/' << reply_info << '/' << reactions;
bool need_update = false;
if (view_count > m->view_count) {
m->view_count = view_count;
need_update = true;
}
if (forward_count > m->forward_count) {
m->forward_count = forward_count;
need_update = true;
}
if (need_update_reply_info) {
if (m->reply_info.channel_id_ != reply_info.channel_id_) {
if (m->reply_info.channel_id_.is_valid() && reply_info.channel_id_.is_valid() && m->message_id.is_server()) {
LOG(ERROR) << "Reply info of " << FullMessageId{dialog_id, m->message_id} << " changed from " << m->reply_info
<< " to " << reply_info << " from " << source;
}
}
m->reply_info = std::move(reply_info);
if (!m->top_thread_message_id.is_valid() && is_thread_message(dialog_id, m)) {
m->top_thread_message_id = m->message_id;
}
need_update |= is_visible_message_reply_info(dialog_id, m);
}
int32 new_dialog_unread_reaction_count = -1;
if (need_update_reactions || need_update_unread_reactions) {
CHECK(m->message_id.is_valid());
int32 unread_reaction_diff = 0;
unread_reaction_diff -= (has_unread_message_reactions(dialog_id, m) ? 1 : 0);
m->reactions = std::move(reactions);
m->available_reactions_generation = d->available_reactions_generation;
unread_reaction_diff += (has_unread_message_reactions(dialog_id, m) ? 1 : 0);
if (is_visible_message_reactions(dialog_id, m)) {
need_update |= need_update_reactions;
if (need_update_unread_reactions) {
if (unread_reaction_diff != 0) {
// remove_message_notification_id(d, m, true, true);
if (d->unread_reaction_count + unread_reaction_diff < 0) {
if (is_dialog_inited(d)) {
LOG(ERROR) << "Unread reaction count of " << dialog_id << " became negative from " << source;
}
} else {
set_dialog_unread_reaction_count(d, d->unread_reaction_count + unread_reaction_diff);
on_dialog_updated(dialog_id, "update_message_interaction_info");
}
}
if (unread_reaction_diff < 0) {
send_update_message_unread_reactions(dialog_id, m, d->unread_reaction_count);
} else {
new_dialog_unread_reaction_count = d->unread_reaction_count;
}
}
}
} else if (has_reactions) {
bool is_changed = false;
if (m->available_reactions_generation != d->available_reactions_generation) {
m->available_reactions_generation = d->available_reactions_generation;
is_changed = true;
}
if (need_update_chosen_reaction_order) {
m->reactions->chosen_reaction_order_ = std::move(reactions->chosen_reaction_order_);
is_changed = true;
}
if (is_changed) {
on_message_changed(d, m, false, "update_message_interaction_info");
}
}
if (need_update) {
send_update_message_interaction_info(dialog_id, m);
}
if (new_dialog_unread_reaction_count >= 0) {
send_update_message_unread_reactions(dialog_id, m, new_dialog_unread_reaction_count);
}
return true;
} else if (has_reactions && m->available_reactions_generation != d->available_reactions_generation) {
m->available_reactions_generation = d->available_reactions_generation;
on_message_changed(d, m, false, "update_message_interaction_info");
}
return false;
}
void MessagesManager::on_update_live_location_viewed(FullMessageId full_message_id) {
LOG(DEBUG) << "Live location was viewed in " << full_message_id;
if (!are_active_live_location_messages_loaded_) {
get_active_live_location_messages(PromiseCreator::lambda([actor_id = actor_id(this), full_message_id](Unit result) {
send_closure(actor_id, &MessagesManager::on_update_live_location_viewed, full_message_id);
}));
return;
}
auto active_live_location_message_ids = get_active_live_location_messages(Auto());
if (!td::contains(active_live_location_message_ids, full_message_id)) {
LOG(DEBUG) << "Can't find " << full_message_id << " in " << active_live_location_message_ids;
return;
}
send_update_message_live_location_viewed(full_message_id);
}
void MessagesManager::on_update_some_live_location_viewed(Promise<Unit> &&promise) {
LOG(DEBUG) << "Some live location was viewed";
if (!are_active_live_location_messages_loaded_) {
get_active_live_location_messages(
PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Unit result) mutable {
send_closure(actor_id, &MessagesManager::on_update_some_live_location_viewed, std::move(promise));
}));
return;
}
// update all live locations, because it is unknown, which exactly was viewed
auto active_live_location_message_ids = get_active_live_location_messages(Auto());
for (const auto &full_message_id : active_live_location_message_ids) {
send_update_message_live_location_viewed(full_message_id);
}
promise.set_value(Unit());
}
void MessagesManager::on_update_message_extended_media(
FullMessageId full_message_id, telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "on_update_message_extended_media");
if (d == nullptr) {
LOG(INFO) << "Ignore update of message extended media in unknown " << dialog_id;
return;
}
auto m = get_message_force(d, full_message_id.get_message_id(), "on_update_message_extended_media");
if (m == nullptr) {
LOG(INFO) << "Ignore update of message extended media in unknown " << full_message_id;
return;
}
auto content = m->content.get();
auto content_type = content->get_type();
if (content_type != MessageContentType::Invoice) {
if (content_type != MessageContentType::Unsupported) {
LOG(ERROR) << "Receive updateMessageExtendedMedia for " << full_message_id << " of type " << content_type;
}
return;
}
if (update_message_content_extended_media(content, std::move(extended_media), dialog_id, td_)) {
send_update_message_content(d, m, true, "on_update_message_extended_media");
on_message_changed(d, m, true, "on_update_message_extended_media");
on_message_notification_changed(d, m, "on_update_message_extended_media"); // usually a no-op
}
}
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) {
Dialog *d = get_dialog(full_message_id.get_dialog_id());
CHECK(d != nullptr);
Message *m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
send_update_message_content(d, m, true, "on_external_update_message_content");
// must not call on_message_changed, because the message itself wasn't changed
if (m->message_id == d->last_message_id) {
send_update_chat_last_message_impl(d, "on_external_update_message_content");
}
on_message_notification_changed(d, m, "on_external_update_message_content");
}
void MessagesManager::on_update_message_content(FullMessageId full_message_id) {
Dialog *d = get_dialog(full_message_id.get_dialog_id());
CHECK(d != nullptr);
Message *m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
send_update_message_content(d, m, true, "on_update_message_content");
on_message_changed(d, m, true, "on_update_message_content");
on_message_notification_changed(d, m, "on_update_message_content");
}
bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention,
const char *source) {
LOG_CHECK(m != nullptr) << source;
CHECK(!m->message_id.is_scheduled());
if (!contains_unread_mention && m->contains_unread_mention) {
remove_message_notification_id(d, m, true, true); // must be called before contains_unread_mention is updated
m->contains_unread_mention = false;
if (d->unread_mention_count == 0) {
if (is_dialog_inited(d)) {
LOG(ERROR) << "Unread mention count of " << d->dialog_id << " became negative from " << source;
}
} else {
set_dialog_unread_mention_count(d, d->unread_mention_count - 1);
on_dialog_updated(d->dialog_id, "update_message_contains_unread_mention");
}
LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count
<< " by reading " << m->message_id << " from " << source;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageMentionRead>(d->dialog_id.get(), m->message_id.get(),
d->unread_mention_count));
return true;
}
return false;
}
bool MessagesManager::remove_message_unread_reactions(Dialog *d, Message *m, const char *source) {
CHECK(m != nullptr);
CHECK(!m->message_id.is_scheduled());
if (!has_unread_message_reactions(d->dialog_id, m)) {
return false;
}
// remove_message_notification_id(d, m, true, true);
m->reactions->unread_reactions_.clear();
if (d->unread_reaction_count == 0) {
if (is_dialog_inited(d)) {
LOG(ERROR) << "Unread reaction count of " << d->dialog_id << " became negative from " << source;
}
} else {
set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1);
on_dialog_updated(d->dialog_id, "remove_message_unread_reactions");
}
LOG(INFO) << "Update unread reaction count in " << d->dialog_id << " to " << d->unread_reaction_count
<< " by reading " << m->message_id << " from " << source;
send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count);
return true;
}
void MessagesManager::on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count,
int32 pts, const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(!max_message_id.is_scheduled());
if (!max_message_id.is_valid() && server_unread_count <= 0) {
return;
}
DialogId dialog_id(channel_id);
Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
LOG(INFO) << "Receive read inbox in unknown " << dialog_id << " from " << source;
return;
}
/*
// dropping unread count can make things worse, so don't drop it
if (server_unread_count > 0 && G()->parameters().use_message_db && d->is_last_read_inbox_message_id_inited) {
server_unread_count = -1;
}
*/
if (d->pts == pts) {
read_history_inbox(dialog_id, max_message_id, server_unread_count, source);
} else if (d->pts > pts) {
// outdated update, need to repair server_unread_count from the server
repair_channel_server_unread_count(d);
} else {
// update from the future, keep it until it can be applied
if (pts >= d->pending_read_channel_inbox_pts) {
if (d->pending_read_channel_inbox_pts == 0) {
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
d->pending_read_channel_inbox_pts = pts;
d->pending_read_channel_inbox_max_message_id = max_message_id;
d->pending_read_channel_inbox_server_unread_count = server_unread_count;
on_dialog_updated(dialog_id, "on_read_channel_inbox");
}
}
}
void MessagesManager::on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id) {
DialogId dialog_id(channel_id);
CHECK(!max_message_id.is_scheduled());
if (max_message_id.is_valid()) {
read_history_outbox(dialog_id, max_message_id);
}
}
void MessagesManager::on_update_channel_max_unavailable_message_id(ChannelId channel_id,
MessageId max_unavailable_message_id) {
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive max_unavailable_message_id in invalid " << channel_id;
return;
}
DialogId dialog_id(channel_id);
CHECK(!max_unavailable_message_id.is_scheduled());
if (!max_unavailable_message_id.is_valid() && max_unavailable_message_id != MessageId()) {
LOG(ERROR) << "Receive wrong max_unavailable_message_id: " << max_unavailable_message_id;
max_unavailable_message_id = MessageId();
}
set_dialog_max_unavailable_message_id(dialog_id, max_unavailable_message_id, true,
"on_update_channel_max_unavailable_message_id");
}
void MessagesManager::on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count,
bool is_from_server) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive number of online members in invalid " << dialog_id;
return;
}
if (is_broadcast_channel(dialog_id)) {
LOG_IF(ERROR, online_member_count != 0)
<< "Receive " << online_member_count << " as a number of online members in a channel " << dialog_id;
return;
}
if (online_member_count < 0) {
LOG(ERROR) << "Receive " << online_member_count << " as a number of online members in a " << dialog_id;
return;
}
set_dialog_online_member_count(dialog_id, online_member_count, is_from_server,
"on_update_channel_online_member_count");
}
void MessagesManager::on_update_delete_scheduled_messages(DialogId dialog_id,
vector<ScheduledServerMessageId> &&server_message_ids) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive deleted scheduled messages in invalid " << dialog_id;
return;
}
Dialog *d = get_dialog_force(dialog_id, "on_update_delete_scheduled_messages");
if (d == nullptr) {
LOG(INFO) << "Skip updateDeleteScheduledMessages in unknown " << dialog_id;
return;
}
vector<int64> deleted_message_ids;
for (auto server_message_id : server_message_ids) {
if (!server_message_id.is_valid()) {
LOG(ERROR) << "Incoming update tries to delete scheduled message " << server_message_id.get();
continue;
}
auto message = do_delete_scheduled_message(d, MessageId(server_message_id, std::numeric_limits<int32>::max()), true,
"on_update_delete_scheduled_messages");
if (message != nullptr) {
deleted_message_ids.push_back(message->message_id.get());
}
}
send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true);
send_update_chat_has_scheduled_messages(d, true);
}
void MessagesManager::on_update_created_public_broadcasts(vector<ChannelId> channel_ids) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (created_public_broadcasts_inited_ && created_public_broadcasts_ == channel_ids) {
return;
}
LOG(INFO) << "Update create public channels to " << channel_ids;
for (auto channel_id : channel_ids) {
force_create_dialog(DialogId(channel_id), "on_update_created_public_broadcasts");
}
created_public_broadcasts_inited_ = true;
created_public_broadcasts_ = std::move(channel_ids);
}
void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id,
DialogAction action, int32 date, MessageContentType message_content_type) {
if (td_->auth_manager_->is_bot() || !typing_dialog_id.is_valid()) {
return;
}
if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) {
LOG(ERROR) << "Ignore " << action << " in the message thread of " << top_thread_message_id;
return;
}
auto dialog_type = dialog_id.get_type();
if (action == DialogAction::get_speaking_action()) {
if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) {
LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id;
return;
}
const Dialog *d = get_dialog_force(dialog_id, "on_dialog_action");
if (d != nullptr && d->active_group_call_id.is_valid()) {
auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id);
td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, typing_dialog_id, date);
}
return;
}
if (is_broadcast_channel(dialog_id)) {
return;
}
auto typing_dialog_type = typing_dialog_id.get_type();
if (typing_dialog_type != DialogType::User && dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) {
LOG(ERROR) << "Ignore " << action << " of " << typing_dialog_id << " in " << dialog_id;
return;
}
{
auto message_import_progress = action.get_importing_messages_action_progress();
if (message_import_progress >= 0) {
// TODO
return;
}
}
{
auto clicking_info = action.get_clicking_animated_emoji_action_info();
if (!clicking_info.data.empty()) {
if (date > G()->unix_time() - 10 && dialog_type == DialogType::User && dialog_id == typing_dialog_id) {
FullMessageId full_message_id{dialog_id, MessageId(ServerMessageId(clicking_info.message_id))};
auto *m = get_message_force(full_message_id, "on_dialog_action");
if (m != nullptr) {
on_message_content_animated_emoji_clicked(m->content.get(), full_message_id, td_,
std::move(clicking_info.emoji), std::move(clicking_info.data));
}
}
return;
}
}
if (is_unsent_animated_emoji_click(td_, dialog_id, action)) {
LOG(DEBUG) << "Ignore unsent " << action;
return;
}
if (!have_dialog(dialog_id)) {
LOG(DEBUG) << "Ignore " << action << " in unknown " << dialog_id;
return;
}
if (typing_dialog_type == DialogType::User) {
if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) {
LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id();
return;
}
} else {
if (!have_dialog_info_force(typing_dialog_id)) {
LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id;
return;
}
force_create_dialog(typing_dialog_id, "on_dialog_action", true);
if (!have_dialog(typing_dialog_id)) {
LOG(ERROR) << "Failed to create typing " << typing_dialog_id;
return;
}
}
bool is_canceled = action == DialogAction();
if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) {
td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date);
}
if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
CHECK(typing_dialog_type == DialogType::User);
auto user_id = typing_dialog_id.get_user_id();
if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) &&
!get_dialog(dialog_id)->is_opened && !is_canceled) {
return;
}
}
if (is_canceled) {
// passed top_thread_message_id must be ignored
auto actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
return;
}
auto &active_actions = actions_it->second;
auto it = std::find_if(
active_actions.begin(), active_actions.end(),
[typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; });
if (it == active_actions.end()) {
return;
}
if (!(typing_dialog_type == DialogType::User &&
td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) &&
!it->action.is_canceled_by_message_of_type(message_content_type)) {
return;
}
LOG(DEBUG) << "Cancel action of " << typing_dialog_id << " in " << dialog_id;
top_thread_message_id = it->top_thread_message_id;
active_actions.erase(it);
if (active_actions.empty()) {
active_dialog_actions_.erase(dialog_id);
LOG(DEBUG) << "Cancel action timeout in " << dialog_id;
active_dialog_action_timeout_.cancel_timeout(dialog_id.get());
}
} else {
if (date < G()->unix_time_cached() - DIALOG_ACTION_TIMEOUT - 60) {
LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date;
return;
}
auto &active_actions = active_dialog_actions_[dialog_id];
auto it = std::find_if(
active_actions.begin(), active_actions.end(),
[typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; });
MessageId prev_top_thread_message_id;
DialogAction prev_action;
if (it != active_actions.end()) {
LOG(DEBUG) << "Re-add action of " << typing_dialog_id << " in " << dialog_id;
prev_top_thread_message_id = it->top_thread_message_id;
prev_action = it->action;
active_actions.erase(it);
} else {
LOG(DEBUG) << "Add action of " << typing_dialog_id << " in " << dialog_id;
}
active_actions.emplace_back(top_thread_message_id, typing_dialog_id, action, Time::now());
if (top_thread_message_id == prev_top_thread_message_id && action == prev_action) {
return;
}
if (top_thread_message_id != prev_top_thread_message_id && prev_top_thread_message_id.is_valid()) {
send_update_chat_action(dialog_id, prev_top_thread_message_id, typing_dialog_id, DialogAction());
}
if (active_actions.size() == 1u) {
LOG(DEBUG) << "Set action timeout in " << dialog_id;
active_dialog_action_timeout_.set_timeout_in(dialog_id.get(), DIALOG_ACTION_TIMEOUT);
}
}
if (top_thread_message_id.is_valid()) {
send_update_chat_action(dialog_id, MessageId(), typing_dialog_id, action);
}
send_update_chat_action(dialog_id, top_thread_message_id, typing_dialog_id, action);
}
void MessagesManager::cancel_dialog_action(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || m->forward_info != nullptr || m->had_forward_info ||
m->via_bot_user_id.is_valid() || m->hide_via_bot || m->is_channel_post || m->message_id.is_scheduled()) {
return;
}
on_dialog_action(dialog_id, MessageId() /*ignored*/, get_message_sender(m), DialogAction(), m->date,
m->content->get_type());
}
void MessagesManager::add_postponed_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update,
int32 new_pts, int32 pts_count, Promise<Unit> &&promise) {
postponed_channel_updates_[dialog_id].emplace(
new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise)));
}
void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update,
int32 new_pts, int32 pts_count, Promise<Unit> &&promise,
const char *source, bool is_postponed_update) {
LOG(INFO) << "Receive from " << source << " pending " << to_string(update);
CHECK(update != nullptr);
if (dialog_id.get_type() != DialogType::Channel) {
LOG(ERROR) << "Receive channel update in invalid " << dialog_id << " from " << source << ": "
<< oneline(to_string(update));
promise.set_value(Unit());
return;
}
if (pts_count < 0 || new_pts <= pts_count) {
LOG(ERROR) << "Receive channel update from " << source << " with wrong pts = " << new_pts
<< " or pts_count = " << pts_count << ": " << oneline(to_string(update));
promise.set_value(Unit());
return;
}
auto channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->have_channel(channel_id) && td_->contacts_manager_->have_min_channel(channel_id)) {
td_->updates_manager_->schedule_get_difference("add_pending_channel_update 1");
promise.set_value(Unit());
return;
}
// TODO need to save all updates that can change result of running queries not associated with pts (for example
// getHistory) and apply them to result of these queries
Dialog *d = get_dialog_force(dialog_id, "add_pending_channel_update 2");
if (d == nullptr) {
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
if (!td_->contacts_manager_->have_channel(channel_id)) {
// do not create dialog if there is no info about the channel
LOG(INFO) << "There is no info about " << channel_id << ", so ignore " << oneline(to_string(update));
promise.set_value(Unit());
return;
}
if (new_pts <= pts && new_pts >= pts - 19999) {
LOG(INFO) << "There is no need to process an update with pts " << new_pts << " in " << dialog_id << " with pts "
<< pts;
promise.set_value(Unit());
return;
}
if (new_pts > pts && pts != new_pts - pts_count) {
LOG(INFO) << "Found a gap in unknown " << dialog_id << " with pts = " << pts << ". new_pts = " << new_pts
<< ", pts_count = " << pts_count << " in update from " << source;
add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
get_channel_difference(dialog_id, pts, true, "add_pending_channel_update 3");
return;
}
d = add_dialog(dialog_id, "add_pending_channel_update 4");
CHECK(d != nullptr);
CHECK(d->pts == pts);
update_dialog_pos(d, "add_pending_channel_update 5");
}
}
if (d == nullptr) {
// if there is no dialog, it can be created by the update
LOG(INFO) << "Receive pending update from " << source << " about unknown " << dialog_id;
if (running_get_channel_difference(dialog_id)) {
add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
return;
}
} else {
int32 old_pts = d->pts;
if (new_pts <= old_pts) { // very old or unuseful update
if (update->get_id() == telegram_api::updateNewChannelMessage::ID) {
auto update_new_channel_message = static_cast<telegram_api::updateNewChannelMessage *>(update.get());
auto message_id = MessageId::get_message_id(update_new_channel_message->message_, false);
FullMessageId full_message_id(dialog_id, message_id);
if (update_message_ids_.count(full_message_id) > 0) {
// apply sent channel message
on_get_message(std::move(update_new_channel_message->message_), true, true, false, true, true,
"updateNewChannelMessage with an awaited message");
promise.set_value(Unit());
return;
}
}
if (update->get_id() == updateSentMessage::ID) {
auto update_sent_message = static_cast<updateSentMessage *>(update.get());
if (being_sent_messages_.count(update_sent_message->random_id_) > 0) {
// apply sent channel message
on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_,
update_sent_message->date_, update_sent_message->ttl_period_, FileId(),
"process old updateSentChannelMessage");
promise.set_value(Unit());
return;
}
}
LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update))
<< "Receive from " << source << " useless channel update " << oneline(to_string(update));
LOG(INFO) << "Skip already applied channel update";
promise.set_value(Unit());
return;
}
if (running_get_channel_difference(dialog_id)) {
LOG(INFO) << "Postpone channel update, because getChannelDifference is run";
add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
return;
}
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->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() ||
is_dialog_sponsored(d)) {
add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
get_channel_difference(dialog_id, old_pts, true, "add_pending_channel_update pts mismatch");
} else {
promise.set_value(Unit());
}
return;
}
}
if (d == nullptr || pts_count > 0) {
if (!process_channel_update(std::move(update)) &&
channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) {
promise.set_value(Unit());
return;
}
LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
} else {
LOG_IF(INFO, update->get_id() != dummyUpdate::ID)
<< "Skip useless channel update from " << source << ": " << to_string(update);
}
if (d == nullptr) {
d = get_dialog(dialog_id);
if (d == nullptr) {
LOG(INFO) << "Update didn't created " << dialog_id;
promise.set_value(Unit());
return;
}
}
CHECK(new_pts > d->pts);
set_channel_pts(d, new_pts, source);
promise.set_value(Unit());
}
bool MessagesManager::is_old_channel_update(DialogId dialog_id, int32 new_pts) {
CHECK(dialog_id.get_type() == DialogType::Channel);
const Dialog *d = get_dialog_force(dialog_id, "is_old_channel_update");
return new_pts <= (d == nullptr ? load_channel_pts(dialog_id) : d->pts);
}
void MessagesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&update_ptr) {
switch (update_ptr->get_id()) {
case dummyUpdate::ID:
LOG(INFO) << "Process dummyUpdate";
break;
case telegram_api::updateNewMessage::ID: {
auto update = move_tl_object_as<telegram_api::updateNewMessage>(update_ptr);
LOG(INFO) << "Process updateNewMessage";
on_get_message(std::move(update->message_), true, false, false, true, true, "updateNewMessage");
break;
}
case updateSentMessage::ID: {
auto update = move_tl_object_as<updateSentMessage>(update_ptr);
LOG(INFO) << "Process updateSentMessage " << update->random_id_;
on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(),
"process updateSentMessage");
break;
}
case telegram_api::updateReadMessagesContents::ID: {
if (td_->auth_manager_->is_bot()) {
break;
}
auto update = move_tl_object_as<telegram_api::updateReadMessagesContents>(update_ptr);
LOG(INFO) << "Process updateReadMessageContents";
for (auto &message_id : update->messages_) {
read_message_content_from_updates(MessageId(ServerMessageId(message_id)));
}
break;
}
case telegram_api::updateEditMessage::ID: {
auto update = move_tl_object_as<telegram_api::updateEditMessage>(update_ptr);
LOG(INFO) << "Process updateEditMessage";
bool had_message =
have_message_force(FullMessageId::get_full_message_id(update->message_, false), "updateEditMessage");
auto full_message_id =
on_get_message(std::move(update->message_), false, false, false, false, false, "updateEditMessage");
on_message_edited(full_message_id, update->pts_, had_message);
break;
}
case telegram_api::updateDeleteMessages::ID: {
auto update = move_tl_object_as<telegram_api::updateDeleteMessages>(update_ptr);
LOG(INFO) << "Process updateDeleteMessages";
vector<MessageId> message_ids;
for (auto message : update->messages_) {
message_ids.push_back(MessageId(ServerMessageId(message)));
}
delete_messages_from_updates(message_ids);
break;
}
case telegram_api::updateReadHistoryInbox::ID: {
auto update = move_tl_object_as<telegram_api::updateReadHistoryInbox>(update_ptr);
LOG(INFO) << "Process updateReadHistoryInbox";
DialogId dialog_id(update->peer_);
FolderId folder_id;
if ((update->flags_ & telegram_api::updateReadHistoryInbox::FOLDER_ID_MASK) != 0) {
folder_id = FolderId(update->folder_id_);
}
on_update_dialog_folder_id(dialog_id, folder_id);
read_history_inbox(dialog_id, MessageId(ServerMessageId(update->max_id_)), -1 /*update->still_unread_count*/,
"updateReadHistoryInbox");
break;
}
case telegram_api::updateReadHistoryOutbox::ID: {
auto update = move_tl_object_as<telegram_api::updateReadHistoryOutbox>(update_ptr);
LOG(INFO) << "Process updateReadHistoryOutbox";
read_history_outbox(DialogId(update->peer_), MessageId(ServerMessageId(update->max_id_)));
break;
}
case telegram_api::updatePinnedMessages::ID: {
auto update = move_tl_object_as<telegram_api::updatePinnedMessages>(update_ptr);
LOG(INFO) << "Process updatePinnedMessages";
vector<MessageId> message_ids;
for (auto message : update->messages_) {
message_ids.push_back(MessageId(ServerMessageId(message)));
}
update_dialog_pinned_messages_from_updates(DialogId(update->peer_), message_ids, update->pinned_);
break;
}
default:
UNREACHABLE();
}
CHECK(!td_->updates_manager_->running_get_difference());
}
bool MessagesManager::process_channel_update(tl_object_ptr<telegram_api::Update> &&update_ptr) {
switch (update_ptr->get_id()) {
case dummyUpdate::ID:
LOG(INFO) << "Process dummyUpdate";
break;
case updateSentMessage::ID: {
auto update = move_tl_object_as<updateSentMessage>(update_ptr);
LOG(INFO) << "Process updateSentMessage " << update->random_id_;
on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(),
"process updateSentChannelMessage");
break;
}
case telegram_api::updateNewChannelMessage::ID: {
auto update = move_tl_object_as<telegram_api::updateNewChannelMessage>(update_ptr);
LOG(INFO) << "Process updateNewChannelMessage";
on_get_message(std::move(update->message_), true, true, false, true, true, "updateNewChannelMessage");
break;
}
case telegram_api::updateDeleteChannelMessages::ID: {
auto update = move_tl_object_as<telegram_api::updateDeleteChannelMessages>(update_ptr);
LOG(INFO) << "Process updateDeleteChannelMessages";
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
break;
}
vector<MessageId> message_ids;
for (auto &message : update->messages_) {
auto message_id = MessageId(ServerMessageId(message));
if (message_id.is_valid()) {
message_ids.push_back(message_id);
} else {
LOG(ERROR) << "Receive updateDeleteChannelMessages with message " << message << " in " << channel_id;
}
}
delete_dialog_messages(DialogId(channel_id), message_ids, true, "updateDeleteChannelMessages");
break;
}
case telegram_api::updateEditChannelMessage::ID: {
auto update = move_tl_object_as<telegram_api::updateEditChannelMessage>(update_ptr);
LOG(INFO) << "Process updateEditChannelMessage";
bool had_message =
have_message_force(FullMessageId::get_full_message_id(update->message_, false), "updateEditChannelMessage");
auto full_message_id =
on_get_message(std::move(update->message_), false, true, false, false, false, "updateEditChannelMessage");
if (full_message_id == FullMessageId()) {
return false;
}
on_message_edited(full_message_id, update->pts_, had_message);
break;
}
case telegram_api::updatePinnedChannelMessages::ID: {
auto update = move_tl_object_as<telegram_api::updatePinnedChannelMessages>(update_ptr);
LOG(INFO) << "Process updatePinnedChannelMessages";
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
break;
}
vector<MessageId> message_ids;
for (auto &message : update->messages_) {
message_ids.push_back(MessageId(ServerMessageId(message)));
}
update_dialog_pinned_messages_from_updates(DialogId(channel_id), message_ids, update->pinned_);
break;
}
default:
UNREACHABLE();
}
return true;
}
void MessagesManager::on_message_edited(FullMessageId full_message_id, int32 pts, bool had_message) {
if (full_message_id == FullMessageId()) {
return;
}
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
Message *m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
m->last_edit_pts = pts;
d->last_edited_message_id = m->message_id;
if (td_->auth_manager_->is_bot()) {
send_update_message_edited(dialog_id, m);
}
update_used_hashtags(dialog_id, m);
if (!had_message &&
((m->reactions != nullptr && !m->reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0)) {
// if new message with unread reactions was added or the chat has unread reactions,
// then number of unread reactions could have been changed, so reload the number of unread reactions
send_get_dialog_query(dialog_id, Promise<Unit>(), 0, "on_message_edited");
}
}
bool MessagesManager::update_dialog_notification_settings(DialogId dialog_id,
DialogNotificationSettings *current_settings,
DialogNotificationSettings &&new_settings) {
if (td_->auth_manager_->is_bot()) {
// just in case
return false;
}
auto need_update = need_update_dialog_notification_settings(current_settings, new_settings);
if (need_update.are_changed) {
Dialog *d = get_dialog(dialog_id);
LOG_CHECK(d != nullptr) << "Wrong " << dialog_id << " in update_dialog_notification_settings";
bool was_dialog_mentions_disabled = is_dialog_mention_notifications_disabled(d);
VLOG(notifications) << "Update notification settings in " << dialog_id << " from " << *current_settings << " to "
<< new_settings;
update_dialog_unmute_timeout(d, current_settings->use_default_mute_until, current_settings->mute_until,
new_settings.use_default_mute_until, new_settings.mute_until);
*current_settings = std::move(new_settings);
on_dialog_updated(dialog_id, "update_dialog_notification_settings");
if (is_dialog_muted(d)) {
// no check for was_muted to clean pending message notifications in chats with unsynchronized settings
remove_all_dialog_notifications(d, false, "update_dialog_notification_settings 2");
}
if (is_dialog_pinned_message_notifications_disabled(d) && d->mention_notification_group.group_id.is_valid() &&
d->pinned_message_notification_message_id.is_valid()) {
remove_dialog_pinned_message_notification(d, "update_dialog_notification_settings 3");
}
if (was_dialog_mentions_disabled != is_dialog_mention_notifications_disabled(d)) {
if (was_dialog_mentions_disabled) {
update_dialog_mention_notification_count(d);
} else {
remove_dialog_mention_notifications(d);
}
}
if (need_update.need_update_server || need_update.need_update_local) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatNotificationSettings>(
dialog_id.get(), get_chat_notification_settings_object(current_settings)));
}
}
return need_update.need_update_server;
}
void MessagesManager::schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until) {
auto now = G()->unix_time_cached();
if (!use_default && mute_until >= now && mute_until < now + 366 * 86400) {
dialog_unmute_timeout_.set_timeout_in(dialog_id.get(), mute_until - now + 1);
} else {
dialog_unmute_timeout_.cancel_timeout(dialog_id.get());
}
}
void MessagesManager::update_dialog_unmute_timeout(Dialog *d, bool &old_use_default, int32 &old_mute_until,
bool new_use_default, int32 new_mute_until) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (old_use_default == new_use_default && old_mute_until == new_mute_until) {
return;
}
CHECK(d != nullptr);
CHECK(old_mute_until >= 0);
schedule_dialog_unmute(d->dialog_id, new_use_default, new_mute_until);
auto scope = get_dialog_notification_setting_scope(d->dialog_id);
auto scope_mute_until = td_->notification_settings_manager_->get_scope_mute_until(scope);
bool was_muted = (old_use_default ? scope_mute_until : old_mute_until) != 0;
bool is_muted = (new_use_default ? scope_mute_until : new_mute_until) != 0;
if (was_muted != is_muted && need_unread_counter(d->order)) {
auto unread_count = d->server_unread_count + d->local_unread_count;
if (unread_count != 0 || d->is_marked_as_unread) {
for (auto &list : get_dialog_lists(d)) {
if (unread_count != 0 && list.is_message_unread_count_inited_) {
int32 delta = was_muted ? -unread_count : unread_count;
list.unread_message_muted_count_ += delta;
send_update_unread_message_count(list, d->dialog_id, true, "update_dialog_unmute_timeout");
}
if (list.is_dialog_unread_count_inited_) {
int32 delta = was_muted ? -1 : 1;
list.unread_dialog_muted_count_ += delta;
if (unread_count == 0 && d->is_marked_as_unread) {
list.unread_dialog_muted_marked_count_ += delta;
}
send_update_unread_chat_count(list, d->dialog_id, true, "update_dialog_unmute_timeout");
}
}
}
}
old_use_default = new_use_default;
old_mute_until = new_mute_until;
if (was_muted != is_muted && !dialog_filters_.empty()) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "update_dialog_unmute_timeout");
}
}
void MessagesManager::on_update_notification_scope_is_muted(NotificationSettingsScope scope, bool is_muted) {
if (G()->parameters().use_message_db) {
std::unordered_map<DialogListId, int32, DialogListIdHash> delta;
std::unordered_map<DialogListId, int32, DialogListIdHash> total_count;
std::unordered_map<DialogListId, int32, DialogListIdHash> marked_count;
std::unordered_set<DialogListId, DialogListIdHash> dialog_list_ids;
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until &&
get_dialog_notification_setting_scope(d->dialog_id) == scope) {
int32 unread_count = d->server_unread_count + d->local_unread_count;
if (unread_count != 0) {
for (auto dialog_list_id : get_dialog_list_ids(d)) {
delta[dialog_list_id] += unread_count;
total_count[dialog_list_id]++;
dialog_list_ids.insert(dialog_list_id);
}
} else if (d->is_marked_as_unread) {
for (auto dialog_list_id : get_dialog_list_ids(d)) {
total_count[dialog_list_id]++;
marked_count[dialog_list_id]++;
dialog_list_ids.insert(dialog_list_id);
}
}
}
});
for (auto dialog_list_id : dialog_list_ids) {
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
if (delta[dialog_list_id] != 0 && list->is_message_unread_count_inited_) {
if (is_muted) {
list->unread_message_muted_count_ += delta[dialog_list_id];
} else {
list->unread_message_muted_count_ -= delta[dialog_list_id];
}
send_update_unread_message_count(*list, DialogId(), true, "on_update_notification_scope_is_muted");
}
if (total_count[dialog_list_id] != 0 && list->is_dialog_unread_count_inited_) {
if (is_muted) {
list->unread_dialog_muted_count_ += total_count[dialog_list_id];
list->unread_dialog_muted_marked_count_ += marked_count[dialog_list_id];
} else {
list->unread_dialog_muted_count_ -= total_count[dialog_list_id];
list->unread_dialog_muted_marked_count_ -= marked_count[dialog_list_id];
}
send_update_unread_chat_count(*list, DialogId(), true, "on_update_notification_scope_is_muted");
}
}
}
if (!dialog_filters_.empty()) {
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until &&
get_dialog_notification_setting_scope(d->dialog_id) == scope) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "on_update_notification_scope_is_muted");
}
});
}
if (is_muted) {
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until &&
get_dialog_notification_setting_scope(d->dialog_id) == scope) {
remove_all_dialog_notifications(d, false, "on_update_notification_scope_is_muted");
}
});
}
}
void MessagesManager::on_dialog_unmute(DialogId dialog_id) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (d->notification_settings.use_default_mute_until) {
return;
}
if (d->notification_settings.mute_until == 0) {
return;
}
auto now = G()->unix_time();
if (d->notification_settings.mute_until > now) {
LOG(ERROR) << "Failed to unmute " << dialog_id << " in " << now << ", will be unmuted in "
<< d->notification_settings.mute_until;
schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until);
return;
}
LOG(INFO) << "Unmute " << dialog_id;
update_dialog_unmute_timeout(d, d->notification_settings.use_default_mute_until, d->notification_settings.mute_until,
false, 0);
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatNotificationSettings>(
dialog_id.get(), get_chat_notification_settings_object(&d->notification_settings)));
on_dialog_updated(dialog_id, "on_dialog_unmute");
}
void MessagesManager::on_update_dialog_notify_settings(
DialogId dialog_id, tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings, const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
VLOG(notifications) << "Receive notification settings for " << dialog_id << " from " << source << ": "
<< to_string(peer_notify_settings);
DialogNotificationSettings *current_settings = get_dialog_notification_settings(dialog_id, true);
if (current_settings == nullptr) {
return;
}
DialogNotificationSettings notification_settings =
::td::get_dialog_notification_settings(std::move(peer_notify_settings), current_settings);
if (!notification_settings.is_synchronized) {
return;
}
update_dialog_notification_settings(dialog_id, current_settings, std::move(notification_settings));
}
void MessagesManager::on_update_dialog_available_reactions(
DialogId dialog_id, telegram_api::object_ptr<telegram_api::ChatReactions> &&available_reactions) {
if (td_->auth_manager_->is_bot()) {
return;
}
Dialog *d = get_dialog_force(dialog_id, "on_update_dialog_available_reactions");
if (d == nullptr) {
return;
}
set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions)));
}
void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
switch (d->dialog_id.get_type()) {
case DialogType::Chat:
case DialogType::Channel:
// ok
break;
case DialogType::User:
case DialogType::SecretChat:
default:
UNREACHABLE();
break;
}
if (d->available_reactions == available_reactions) {
if (!d->is_available_reactions_inited) {
d->is_available_reactions_inited = true;
on_dialog_updated(d->dialog_id, "set_dialog_available_reactions");
}
return;
}
VLOG(notifications) << "Update available reactions in " << d->dialog_id << " to " << available_reactions;
auto old_active_reactions = get_active_reactions(d->available_reactions);
auto new_active_reactions = get_active_reactions(available_reactions);
bool need_update = old_active_reactions != new_active_reactions;
bool need_update_message_reactions_visibility = old_active_reactions.empty() != new_active_reactions.empty();
d->available_reactions = std::move(available_reactions);
d->is_available_reactions_inited = true;
if (need_update_message_reactions_visibility) {
if (!old_active_reactions.empty()) {
hide_dialog_message_reactions(d);
}
set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
}
on_dialog_updated(d->dialog_id, "set_dialog_available_reactions");
if (need_update) {
send_update_chat_available_reactions(d);
}
}
void MessagesManager::set_dialog_next_available_reactions_generation(Dialog *d, uint32 generation) {
CHECK(d != nullptr);
switch (d->dialog_id.get_type()) {
case DialogType::Chat:
case DialogType::Channel:
// ok
break;
case DialogType::User:
case DialogType::SecretChat:
default:
UNREACHABLE();
break;
}
if (get_active_reactions(d->available_reactions).empty()) {
// 0 -> 1
// 1 -> 3
generation = generation + (generation & 1) + 1;
} else {
// 0 -> 2
// 1 -> 2
generation = generation - (generation & 1) + 2;
}
LOG(INFO) << "Change available reactions generation from " << d->available_reactions_generation << " to "
<< generation << " in " << d->dialog_id;
d->available_reactions_generation = generation;
}
void MessagesManager::hide_dialog_message_reactions(Dialog *d) {
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids,
[](const Message *m) { return m->reactions != nullptr && !m->reactions->reactions_.empty(); });
for (auto message_id : message_ids) {
Message *m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->reactions != nullptr);
bool need_update_unread_reactions = !m->reactions->unread_reactions_.empty();
m->reactions = nullptr;
// don't resave all messages to the database
if (need_update_unread_reactions) {
send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count);
}
send_update_message_interaction_info(d->dialog_id, m);
}
if (d->unread_reaction_count != 0) {
set_dialog_unread_reaction_count(d, 0);
}
}
void MessagesManager::set_active_reactions(vector<string> active_reactions) {
if (active_reactions == active_reactions_) {
return;
}
LOG(INFO) << "Set active reactions to " << active_reactions;
bool is_changed = active_reactions != active_reactions_;
active_reactions_ = std::move(active_reactions);
auto old_active_reaction_pos_ = std::move(active_reaction_pos_);
active_reaction_pos_.clear();
for (size_t i = 0; i < active_reactions_.size(); i++) {
active_reaction_pos_[active_reactions_[i]] = i;
}
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
switch (dialog_id.get_type()) {
case DialogType::User:
if (is_changed) {
send_update_chat_available_reactions(d);
}
break;
case DialogType::Chat:
case DialogType::Channel: {
auto old_reactions = d->available_reactions.get_active_reactions(old_active_reaction_pos_);
auto new_reactions = d->available_reactions.get_active_reactions(active_reaction_pos_);
if (old_reactions != new_reactions) {
if (old_reactions.empty() != new_reactions.empty()) {
if (!old_reactions.empty()) {
hide_dialog_message_reactions(d);
}
set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
on_dialog_updated(d->dialog_id, "set_active_reactions");
}
send_update_chat_available_reactions(d);
}
break;
}
case DialogType::SecretChat:
break;
default:
UNREACHABLE();
break;
}
});
}
ChatReactions MessagesManager::get_active_reactions(const ChatReactions &available_reactions) const {
return available_reactions.get_active_reactions(active_reaction_pos_);
}
ChatReactions MessagesManager::get_dialog_active_reactions(const Dialog *d) const {
CHECK(d != nullptr);
switch (d->dialog_id.get_type()) {
case DialogType::User:
return ChatReactions(true, true);
case DialogType::Chat:
case DialogType::Channel:
return get_active_reactions(d->available_reactions);
case DialogType::SecretChat:
return {};
default:
UNREACHABLE();
return {};
}
}
ChatReactions MessagesManager::get_message_active_reactions(const Dialog *d, const Message *m) const {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (is_service_message_content(m->content->get_type()) || m->ttl > 0) {
return ChatReactions();
}
if (is_discussion_message(d->dialog_id, m)) {
d = get_dialog(m->forward_info->from_dialog_id);
if (d == nullptr) {
LOG(ERROR) << "Failed to find linked " << m->forward_info->from_dialog_id
<< " to determine correct active reactions";
return ChatReactions();
}
}
return get_dialog_active_reactions(d);
}
bool MessagesManager::need_poll_dialog_message_reactions(const Dialog *d) {
CHECK(d != nullptr);
switch (d->dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
return false;
case DialogType::Chat:
case DialogType::Channel:
return (d->available_reactions_generation & 1) == 0;
default:
UNREACHABLE();
return {};
}
}
bool MessagesManager::need_poll_message_reactions(const Dialog *d, const Message *m) {
CHECK(m != nullptr);
if (!m->message_id.is_valid() || !m->message_id.is_server()) {
return false;
}
if (!need_poll_dialog_message_reactions(d)) {
return false;
}
if (m->reactions != nullptr) {
return true;
}
if (m->available_reactions_generation == d->available_reactions_generation) {
return false;
}
if (is_service_message_content(m->content->get_type())) {
return false;
}
return true;
}
void MessagesManager::queue_message_reactions_reload(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
CHECK(dialog_id.is_valid());
auto message_id = full_message_id.get_message_id();
CHECK(message_id.is_valid());
being_reloaded_reactions_[dialog_id].message_ids.insert(message_id);
try_reload_message_reactions(dialog_id, false);
}
void MessagesManager::queue_message_reactions_reload(DialogId dialog_id, const vector<MessageId> &message_ids) {
LOG(INFO) << "Queue reload of reactions in " << message_ids << " in " << dialog_id;
auto &message_ids_to_reload = being_reloaded_reactions_[dialog_id].message_ids;
for (auto &message_id : message_ids) {
CHECK(message_id.is_valid());
message_ids_to_reload.insert(message_id);
}
try_reload_message_reactions(dialog_id, false);
}
void MessagesManager::try_reload_message_reactions(DialogId dialog_id, bool is_finished) {
if (G()->close_flag()) {
return;
}
auto it = being_reloaded_reactions_.find(dialog_id);
if (it == being_reloaded_reactions_.end()) {
return;
}
if (is_finished) {
CHECK(it->second.is_request_sent);
it->second.is_request_sent = false;
if (it->second.message_ids.empty()) {
being_reloaded_reactions_.erase(it);
return;
}
} else if (it->second.is_request_sent) {
return;
}
CHECK(!it->second.message_ids.empty());
CHECK(!it->second.is_request_sent);
it->second.is_request_sent = true;
static constexpr size_t MAX_MESSAGE_IDS = 100; // server-side limit
vector<MessageId> message_ids;
for (auto message_id_it = it->second.message_ids.begin();
message_id_it != it->second.message_ids.end() && message_ids.size() < MAX_MESSAGE_IDS; ++message_id_it) {
message_ids.push_back(*message_id_it);
}
for (auto message_id : message_ids) {
it->second.message_ids.erase(message_id);
}
reload_message_reactions(td_, dialog_id, std::move(message_ids));
}
bool MessagesManager::update_dialog_silent_send_message(Dialog *d, bool silent_send_message) {
if (td_->auth_manager_->is_bot()) {
// just in case
return false;
}
CHECK(d != nullptr);
LOG_IF(WARNING, !d->notification_settings.is_synchronized)
<< "Have unknown notification settings in " << d->dialog_id;
if (d->notification_settings.silent_send_message == silent_send_message) {
return false;
}
LOG(INFO) << "Update silent send message in " << d->dialog_id << " to " << silent_send_message;
d->notification_settings.silent_send_message = silent_send_message;
on_dialog_updated(d->dialog_id, "update_dialog_silent_send_message");
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatDefaultDisableNotification>(d->dialog_id.get(), silent_send_message));
return true;
}
void MessagesManager::reget_dialog_action_bar(DialogId dialog_id, const char *source, bool is_repair) {
if (G()->close_flag() || !dialog_id.is_valid() || td_->auth_manager_->is_bot()) {
return;
}
Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
return;
}
if (is_repair && !d->need_repair_action_bar) {
d->need_repair_action_bar = true;
on_dialog_updated(dialog_id, source);
}
LOG(INFO) << "Reget action bar in " << dialog_id << " from " << source;
switch (dialog_id.get_type()) {
case DialogType::User:
td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto());
return;
case DialogType::Chat:
case DialogType::Channel:
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
return td_->create_handler<GetPeerSettingsQuery>()->send(dialog_id);
case DialogType::SecretChat:
case DialogType::None:
default:
UNREACHABLE();
}
}
void MessagesManager::repair_dialog_action_bar(Dialog *d, const char *source) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
d->need_repair_action_bar = true;
if (have_input_peer(dialog_id, AccessRights::Read)) {
create_actor<SleepActor>(
"RepairChatActionBarActor", 1.0,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, source](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::reget_dialog_action_bar, dialog_id, source, true);
}))
.release();
}
// there is no need to change action bar
on_dialog_updated(dialog_id, source);
}
void MessagesManager::hide_dialog_action_bar(DialogId dialog_id) {
Dialog *d = get_dialog_force(dialog_id, "hide_dialog_action_bar");
if (d == nullptr) {
return;
}
hide_dialog_action_bar(d);
}
void MessagesManager::hide_dialog_action_bar(Dialog *d) {
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
if (!d->know_action_bar) {
return;
}
if (d->need_repair_action_bar) {
d->need_repair_action_bar = false;
on_dialog_updated(d->dialog_id, "hide_dialog_action_bar");
}
if (d->action_bar == nullptr) {
return;
}
d->action_bar = nullptr;
send_update_chat_action_bar(d);
}
void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "remove_dialog_action_bar");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (dialog_id.get_type() == DialogType::SecretChat) {
dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
d = get_dialog_force(dialog_id, "remove_dialog_action_bar 2");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat with the user not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
}
if (!d->know_action_bar) {
return promise.set_error(Status::Error(400, "Can't update chat action bar"));
}
if (d->need_repair_action_bar) {
d->need_repair_action_bar = false;
on_dialog_updated(dialog_id, "remove_dialog_action_bar");
}
if (d->action_bar == nullptr) {
return promise.set_value(Unit());
}
d->action_bar = nullptr;
send_update_chat_action_bar(d);
toggle_dialog_report_spam_state_on_server(dialog_id, false, 0, std::move(promise));
}
void MessagesManager::repair_dialog_active_group_call_id(DialogId dialog_id) {
if (have_input_peer(dialog_id, AccessRights::Read)) {
LOG(INFO) << "Repair active voice chat ID in " << dialog_id;
create_actor<SleepActor>("RepairChatActiveVoiceChatId", 1.0,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::do_repair_dialog_active_group_call_id,
dialog_id);
}))
.release();
}
}
void MessagesManager::do_repair_dialog_active_group_call_id(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
bool need_repair_active_group_call_id = d->has_active_group_call && !d->active_group_call_id.is_valid();
bool need_repair_expected_group_call_id =
d->has_expected_active_group_call_id && d->active_group_call_id != d->expected_active_group_call_id;
d->has_expected_active_group_call_id = false;
if (!need_repair_active_group_call_id && !need_repair_expected_group_call_id) {
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
reload_dialog_info_full(dialog_id, "do_repair_dialog_active_group_call_id");
}
class MessagesManager::ToggleDialogReportSpamStateOnServerLogEvent {
public:
DialogId dialog_id_;
bool is_spam_dialog_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(is_spam_dialog_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(is_spam_dialog_, parser);
}
};
uint64 MessagesManager::save_toggle_dialog_report_spam_state_on_server_log_event(DialogId dialog_id,
bool is_spam_dialog) {
ToggleDialogReportSpamStateOnServerLogEvent log_event{dialog_id, is_spam_dialog};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::toggle_dialog_report_spam_state_on_server(DialogId dialog_id, bool is_spam_dialog,
uint64 log_event_id, Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_toggle_dialog_report_spam_state_on_server_log_event(dialog_id, is_spam_dialog);
}
auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
promise = std::move(new_promise); // to prevent self-move
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
return td_->create_handler<UpdatePeerSettingsQuery>(std::move(promise))->send(dialog_id, is_spam_dialog);
case DialogType::SecretChat:
if (is_spam_dialog) {
return td_->create_handler<ReportEncryptedSpamQuery>(std::move(promise))->send(dialog_id);
} else {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (!user_id.is_valid()) {
return promise.set_error(Status::Error(400, "Peer user not found"));
}
return td_->create_handler<UpdatePeerSettingsQuery>(std::move(promise))->send(DialogId(user_id), false);
}
case DialogType::None:
default:
UNREACHABLE();
return;
}
}
bool MessagesManager::can_report_dialog(DialogId dialog_id) const {
// doesn't include possibility of report from action bar
switch (dialog_id.get_type()) {
case DialogType::User:
return td_->contacts_manager_->can_report_user(dialog_id.get_user_id());
case DialogType::Chat:
return false;
case DialogType::Channel:
return !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_creator();
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
}
void MessagesManager::report_dialog(DialogId dialog_id, const vector<MessageId> &message_ids, ReportReason &&reason,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "report_dialog");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
Dialog *user_d = d;
bool is_dialog_spam_report = false;
bool can_report_spam = false;
if (reason.is_spam() && message_ids.empty()) {
// report from action bar
if (dialog_id.get_type() == DialogType::SecretChat) {
auto user_dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
user_d = get_dialog_force(user_dialog_id, "report_dialog 2");
if (user_d == nullptr) {
return promise.set_error(Status::Error(400, "Chat with the user not found"));
}
}
is_dialog_spam_report = user_d->know_action_bar;
can_report_spam = user_d->action_bar != nullptr && user_d->action_bar->can_report_spam();
}
if (is_dialog_spam_report && can_report_spam) {
hide_dialog_action_bar(user_d);
return toggle_dialog_report_spam_state_on_server(dialog_id, true, 0, std::move(promise));
}
if (!can_report_dialog(dialog_id)) {
if (is_dialog_spam_report) {
return promise.set_value(Unit());
}
return promise.set_error(Status::Error(400, "Chat can't be reported"));
}
vector<MessageId> server_message_ids;
for (auto message_id : message_ids) {
if (message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Can't report scheduled messages"));
}
if (message_id.is_valid() && message_id.is_server()) {
server_message_ids.push_back(message_id);
}
}
if (dialog_id.get_type() == DialogType::Channel && reason.is_unrelated_location()) {
hide_dialog_action_bar(d);
}
td_->create_handler<ReportPeerQuery>(std::move(promise))->send(dialog_id, server_message_ids, std::move(reason));
}
void MessagesManager::report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "report_dialog_photo");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (!can_report_dialog(dialog_id)) {
return promise.set_error(Status::Error(400, "Chat photo can't be reported"));
}
auto file_view = td_->file_manager_->get_file_view(file_id);
if (file_view.empty()) {
return promise.set_error(Status::Error(400, "Unknown file ID"));
}
if (get_main_file_type(file_view.get_type()) != FileType::Photo || !file_view.has_remote_location() ||
!file_view.remote_location().is_photo()) {
return promise.set_error(Status::Error(400, "Only full chat photos can be reported"));
}
td_->create_handler<ReportProfilePhotoQuery>(std::move(promise))
->send(dialog_id, file_id, file_view.remote_location().as_input_photo(), std::move(reason));
}
void MessagesManager::on_get_peer_settings(DialogId dialog_id,
tl_object_ptr<telegram_api::peerSettings> &&peer_settings,
bool ignore_privacy_exception) {
CHECK(peer_settings != nullptr);
if (td_->auth_manager_->is_bot()) {
return;
}
if (dialog_id.get_type() == DialogType::User && !ignore_privacy_exception) {
td_->contacts_manager_->on_update_user_need_phone_number_privacy_exception(dialog_id.get_user_id(),
peer_settings->need_contacts_exception_);
}
Dialog *d = get_dialog_force(dialog_id, "on_get_peer_settings");
if (d == nullptr) {
return;
}
auto distance =
(peer_settings->flags_ & telegram_api::peerSettings::GEO_DISTANCE_MASK) != 0 ? peer_settings->geo_distance_ : -1;
if (distance < -1 || d->has_outgoing_messages) {
distance = -1;
}
auto action_bar =
DialogActionBar::create(peer_settings->report_spam_, peer_settings->add_contact_, peer_settings->block_contact_,
peer_settings->share_contact_, peer_settings->report_geo_, peer_settings->autoarchived_,
distance, peer_settings->invite_members_, peer_settings->request_chat_title_,
peer_settings->request_chat_broadcast_, peer_settings->request_chat_date_);
fix_dialog_action_bar(d, action_bar.get());
if (d->action_bar == action_bar) {
if (!d->know_action_bar || d->need_repair_action_bar) {
d->know_action_bar = true;
d->need_repair_action_bar = false;
on_dialog_updated(d->dialog_id, "on_get_peer_settings");
}
return;
}
d->know_action_bar = true;
d->need_repair_action_bar = false;
d->action_bar = std::move(action_bar);
send_update_chat_action_bar(d);
}
void MessagesManager::fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar) {
if (action_bar == nullptr) {
return;
}
CHECK(d != nullptr);
action_bar->fix(td_, d->dialog_id, d->is_blocked, d->folder_id);
}
Result<string> MessagesManager::get_login_button_url(FullMessageId full_message_id, int64 button_id) {
Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), "get_login_button_url");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
auto m = get_message_force(d, full_message_id.get_message_id(), "get_login_button_url");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
return Status::Error(400, "Message has no inline keyboard");
}
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Can't use login buttons from scheduled messages");
}
if (!m->message_id.is_server()) {
// it shouldn't have UrlAuth buttons anyway
return Status::Error(400, "Message is not server");
}
if (d->dialog_id.get_type() == DialogType::SecretChat) {
// secret chat messages can't have reply markup, so this shouldn't happen now
return Status::Error(400, "Message is in a secret chat");
}
if (button_id < std::numeric_limits<int32>::min() || button_id > std::numeric_limits<int32>::max()) {
return Status::Error(400, "Invalid button identifier specified");
}
for (auto &row : m->reply_markup->inline_keyboard) {
for (auto &button : row) {
if (button.type == InlineKeyboardButton::Type::UrlAuth && button.id == button_id) {
return button.data;
}
}
}
return Status::Error(400, "Button not found");
}
void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) {
class Callback final : public FileManager::DownloadCallback {
public:
explicit Callback(Promise<Unit> download_promise) : download_promise_(std::move(download_promise)) {
}
void on_download_ok(FileId file_id) final {
download_promise_.set_value(Unit());
}
void on_download_error(FileId file_id, Status error) final {
download_promise_.set_error(std::move(error));
}
private:
Promise<Unit> download_promise_;
};
auto thumbnail_promise = PromiseCreator::lambda([actor_id = actor_id(this),
thumbnail_file_id](Result<BufferSlice> r_thumbnail) {
BufferSlice thumbnail_slice;
if (r_thumbnail.is_ok()) {
thumbnail_slice = r_thumbnail.move_as_ok();
}
send_closure(actor_id, &MessagesManager::on_load_secret_thumbnail, thumbnail_file_id, std::move(thumbnail_slice));
});
auto download_promise = PromiseCreator::lambda(
[thumbnail_file_id, thumbnail_promise = std::move(thumbnail_promise)](Result<Unit> r_download) mutable {
if (r_download.is_error()) {
thumbnail_promise.set_error(r_download.move_as_error());
return;
}
send_closure(G()->file_manager(), &FileManager::get_content, thumbnail_file_id, std::move(thumbnail_promise));
});
send_closure(G()->file_manager(), &FileManager::download, thumbnail_file_id,
std::make_shared<Callback>(std::move(download_promise)), 1, -1, -1,
Promise<td_api::object_ptr<td_api::file>>());
}
void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file) {
LOG(INFO) << "File " << file_id << " has been uploaded";
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
// callback may be called just before the file upload was canceled
return;
}
auto full_message_id = it->second.first;
auto thumbnail_file_id = it->second.second;
being_uploaded_files_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
// file upload should be already canceled in cancel_send_message_query, it shouldn't happen
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
bool is_edit = m->message_id.is_any_server();
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
if (!is_edit && can_send_status.is_error()) {
// user has left the chat during upload of the file or lost their privileges
LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
fail_send_message(full_message_id, std::move(can_send_status));
return;
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
if (input_file && thumbnail_file_id.is_valid()) {
// TODO: download thumbnail if needed (like in secret chats)
LOG(INFO) << "Ask to upload thumbnail " << thumbnail_file_id;
bool is_inserted =
being_uploaded_thumbnails_
.emplace(thumbnail_file_id, UploadedThumbnailInfo{full_message_id, file_id, std::move(input_file)})
.second;
CHECK(is_inserted);
td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 32, m->message_id.get());
} else {
do_send_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), nullptr);
}
break;
case DialogType::SecretChat:
if (thumbnail_file_id.is_valid()) {
LOG(INFO) << "Ask to load thumbnail " << thumbnail_file_id;
bool is_inserted = being_loaded_secret_thumbnails_
.emplace(thumbnail_file_id, UploadedSecretThumbnailInfo{full_message_id, file_id,
std::move(input_encrypted_file)})
.second;
CHECK(is_inserted);
load_secret_thumbnail(thumbnail_file_id);
} else {
do_send_secret_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_encrypted_file), BufferSlice());
}
break;
case DialogType::None:
default:
UNREACHABLE();
break;
}
}
void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
tl_object_ptr<telegram_api::InputFile> input_thumbnail) {
CHECK(m != nullptr);
bool have_input_file = input_file != nullptr;
bool have_input_thumbnail = input_thumbnail != nullptr;
LOG(INFO) << "Do send media file " << file_id << " with thumbnail " << thumbnail_file_id
<< ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail
<< ", TTL = " << m->ttl;
MessageContent *content = nullptr;
if (m->message_id.is_any_server()) {
content = m->edited_content.get();
if (content == nullptr) {
LOG(ERROR) << "Message has no edited content";
return;
}
} else {
content = m->content.get();
}
auto input_media = get_input_media(content, td_, std::move(input_file), std::move(input_thumbnail), file_id,
thumbnail_file_id, m->ttl, true);
LOG_CHECK(input_media != nullptr) << to_string(get_message_object(dialog_id, m, "do_send_media")) << ' '
<< have_input_file << ' ' << have_input_thumbnail << ' ' << file_id << ' '
<< thumbnail_file_id << ' ' << m->ttl;
on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id);
}
void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file,
BufferSlice thumbnail) {
CHECK(dialog_id.get_type() == DialogType::SecretChat);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
CHECK(m->message_id.is_yet_unsent());
bool have_input_file = input_encrypted_file != nullptr;
LOG(INFO) << "Do send secret media file " << file_id << " with thumbnail " << thumbnail_file_id
<< ", have_input_file = " << have_input_file;
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
on_secret_message_media_uploaded(
dialog_id, m,
get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail), layer),
file_id, thumbnail_file_id);
}
void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
if (G()->close_flag()) {
// do not fail upload if closing
return;
}
LOG(WARNING) << "File " << file_id << " has upload error " << status;
CHECK(status.is_error());
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
// callback may be called just before the file upload was canceled
return;
}
auto full_message_id = it->second.first;
being_uploaded_files_.erase(it);
bool is_edit = full_message_id.get_message_id().is_any_server();
if (is_edit) {
fail_edit_message_media(full_message_id, std::move(status));
} else {
fail_send_message(full_message_id, std::move(status));
}
}
void MessagesManager::on_load_secret_thumbnail(FileId thumbnail_file_id, BufferSlice thumbnail) {
if (G()->close_flag()) {
// do not send secret media if closing, thumbnail may be wrong
return;
}
LOG(INFO) << "SecretThumbnail " << thumbnail_file_id << " has been loaded with size " << thumbnail.size();
auto it = being_loaded_secret_thumbnails_.find(thumbnail_file_id);
if (it == being_loaded_secret_thumbnails_.end()) {
// just in case, as in on_upload_thumbnail
return;
}
auto full_message_id = it->second.full_message_id;
auto file_id = it->second.file_id;
auto input_file = std::move(it->second.input_file);
being_loaded_secret_thumbnails_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user, do not need to send it
// cancel file upload of the main file to allow next upload with the same file to succeed
LOG(INFO) << "Message with a media has already been deleted";
cancel_upload_file(file_id, "on_load_secret_thumbnail");
return;
}
CHECK(m->message_id.is_yet_unsent());
if (thumbnail.empty()) {
delete_message_content_thumbnail(m->content.get(), td_);
}
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
if (can_send_status.is_error()) {
// secret chat was closed during load of the file
LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
fail_send_message(full_message_id, std::move(can_send_status));
return;
}
do_send_secret_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), std::move(thumbnail));
}
void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputFile> thumbnail_input_file) {
if (G()->close_flag()) {
// do not fail upload if closing
return;
}
LOG(INFO) << "Thumbnail " << thumbnail_file_id << " has been uploaded as " << to_string(thumbnail_input_file);
auto it = being_uploaded_thumbnails_.find(thumbnail_file_id);
if (it == being_uploaded_thumbnails_.end()) {
// callback may be called just before the thumbnail upload was canceled
return;
}
auto full_message_id = it->second.full_message_id;
auto file_id = it->second.file_id;
auto input_file = std::move(it->second.input_file);
being_uploaded_thumbnails_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
// thumbnail file upload should be already canceled in cancel_send_message_query
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
bool is_edit = m->message_id.is_any_server();
if (thumbnail_input_file == nullptr) {
delete_message_content_thumbnail(is_edit ? m->edited_content.get() : m->content.get(), td_);
}
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
if (!is_edit && can_send_status.is_error()) {
// user has left the chat during upload of the thumbnail or lost their privileges
LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
fail_send_message(full_message_id, std::move(can_send_status));
return;
}
do_send_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), std::move(thumbnail_input_file));
}
void MessagesManager::on_upload_dialog_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
LOG(INFO) << "File " << file_id << " has been uploaded";
auto it = being_uploaded_dialog_photos_.find(file_id);
if (it == being_uploaded_dialog_photos_.end()) {
// just in case, as in on_upload_media
return;
}
DialogId dialog_id = it->second.dialog_id;
double main_frame_timestamp = it->second.main_frame_timestamp;
bool is_animation = it->second.is_animation;
bool is_reupload = it->second.is_reupload;
Promise<Unit> promise = std::move(it->second.promise);
being_uploaded_dialog_photos_.erase(it);
FileView file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.is_encrypted());
if (input_file == nullptr && file_view.has_remote_location()) {
if (file_view.main_remote_location().is_web()) {
return promise.set_error(Status::Error(400, "Can't use web photo as profile photo"));
}
if (is_reupload) {
return promise.set_error(Status::Error(400, "Failed to reupload the file"));
}
if (is_animation) {
CHECK(file_view.get_type() == FileType::Animation);
// delete file reference and forcely reupload the file
auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
td_->file_manager_->delete_file_reference(file_id, file_reference);
upload_dialog_photo(dialog_id, file_id, is_animation, main_frame_timestamp, true, std::move(promise), {-1});
} else {
CHECK(file_view.get_type() == FileType::Photo);
auto input_photo = file_view.main_remote_location().as_input_photo();
auto input_chat_photo = make_tl_object<telegram_api::inputChatPhoto>(std::move(input_photo));
send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
}
return;
}
CHECK(input_file != nullptr);
int32 flags = 0;
tl_object_ptr<telegram_api::InputFile> photo_input_file;
tl_object_ptr<telegram_api::InputFile> video_input_file;
if (is_animation) {
flags |= telegram_api::inputChatUploadedPhoto::VIDEO_MASK;
video_input_file = std::move(input_file);
if (main_frame_timestamp != 0.0) {
flags |= telegram_api::inputChatUploadedPhoto::VIDEO_START_TS_MASK;
}
} else {
flags |= telegram_api::inputChatUploadedPhoto::FILE_MASK;
photo_input_file = std::move(input_file);
}
auto input_chat_photo = make_tl_object<telegram_api::inputChatUploadedPhoto>(
flags, std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp);
send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
}
void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status) {
if (G()->close_flag()) {
// do not fail upload if closing
return;
}
LOG(INFO) << "File " << file_id << " has upload error " << status;
CHECK(status.is_error());
auto it = being_uploaded_dialog_photos_.find(file_id);
if (it == being_uploaded_dialog_photos_.end()) {
// just in case, as in on_upload_media_error
return;
}
Promise<Unit> promise = std::move(it->second.promise);
being_uploaded_dialog_photos_.erase(it);
promise.set_error(std::move(status));
}
void MessagesManager::on_upload_imported_messages(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
LOG(INFO) << "File " << file_id << " has been uploaded";
auto it = being_uploaded_imported_messages_.find(file_id);
if (it == being_uploaded_imported_messages_.end()) {
// just in case, as in on_upload_media
return;
}
CHECK(it->second != nullptr);
DialogId dialog_id = it->second->dialog_id;
vector<FileId> attached_file_ids = std::move(it->second->attached_file_ids);
bool is_reupload = it->second->is_reupload;
Promise<Unit> promise = std::move(it->second->promise);
being_uploaded_imported_messages_.erase(it);
TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
FileView file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.is_encrypted());
if (input_file == nullptr && file_view.has_remote_location()) {
if (file_view.main_remote_location().is_web()) {
return promise.set_error(Status::Error(400, "Can't use web file"));
}
if (is_reupload) {
return promise.set_error(Status::Error(400, "Failed to reupload the file"));
}
CHECK(file_view.get_type() == FileType::Document);
// delete file reference and forcely reupload the file
auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
td_->file_manager_->delete_file_reference(file_id, file_reference);
upload_imported_messages(dialog_id, file_id, std::move(attached_file_ids), true, std::move(promise), {-1});
return;
}
CHECK(input_file != nullptr);
td_->create_handler<InitHistoryImportQuery>(std::move(promise))
->send(dialog_id, file_id, std::move(input_file), std::move(attached_file_ids));
}
void MessagesManager::on_upload_imported_messages_error(FileId file_id, Status status) {
if (G()->close_flag()) {
// do not fail upload if closing
return;
}
LOG(INFO) << "File " << file_id << " has upload error " << status;
CHECK(status.is_error());
auto it = being_uploaded_imported_messages_.find(file_id);
if (it == being_uploaded_imported_messages_.end()) {
// just in case, as in on_upload_media_error
return;
}
Promise<Unit> promise = std::move(it->second->promise);
being_uploaded_imported_messages_.erase(it);
promise.set_error(std::move(status));
}
void MessagesManager::on_upload_imported_message_attachment(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file) {
LOG(INFO) << "File " << file_id << " has been uploaded";
auto it = being_uploaded_imported_message_attachments_.find(file_id);
if (it == being_uploaded_imported_message_attachments_.end()) {
// just in case, as in on_upload_media
return;
}
CHECK(it->second != nullptr);
DialogId dialog_id = it->second->dialog_id;
int64 import_id = it->second->import_id;
bool is_reupload = it->second->is_reupload;
Promise<Unit> promise = std::move(it->second->promise);
being_uploaded_imported_message_attachments_.erase(it);
FileView file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.is_encrypted());
if (input_file == nullptr && file_view.has_remote_location()) {
if (file_view.main_remote_location().is_web()) {
return promise.set_error(Status::Error(400, "Can't use web file"));
}
if (is_reupload) {
return promise.set_error(Status::Error(400, "Failed to reupload the file"));
}
// delete file reference and forcely reupload the file
auto file_reference =
file_view.get_type() == FileType::Photo
? FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo())
: FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
td_->file_manager_->delete_file_reference(file_id, file_reference);
upload_imported_message_attachment(dialog_id, import_id, file_id, true, std::move(promise), {-1});
return;
}
CHECK(input_file != nullptr);
auto suggested_path = file_view.suggested_path();
const PathView path_view(suggested_path);
td_->create_handler<UploadImportedMediaQuery>(std::move(promise))
->send(dialog_id, import_id, path_view.file_name().str(), file_id,
get_fake_input_media(td_, std::move(input_file), file_id));
}
void MessagesManager::on_upload_imported_message_attachment_error(FileId file_id, Status status) {
if (G()->close_flag()) {
// do not fail upload if closing
return;
}
LOG(INFO) << "File " << file_id << " has upload error " << status;
CHECK(status.is_error());
auto it = being_uploaded_imported_message_attachments_.find(file_id);
if (it == being_uploaded_imported_message_attachments_.end()) {
// just in case, as in on_upload_media_error
return;
}
Promise<Unit> promise = std::move(it->second->promise);
being_uploaded_imported_message_attachments_.erase(it);
promise.set_error(std::move(status));
}
void MessagesManager::before_get_difference() {
running_get_difference_ = true;
// scheduled messages are not returned in getDifference, so we must always reget them after it
scheduled_messages_sync_generation_++;
}
void MessagesManager::after_get_difference() {
CHECK(!td_->updates_manager_->running_get_difference());
running_get_difference_ = false;
if (!pending_on_get_dialogs_.empty()) {
LOG(INFO) << "Apply postponed results of getDialogs";
for (auto &res : pending_on_get_dialogs_) {
on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages),
std::move(res.promise));
}
pending_on_get_dialogs_.clear();
}
if (!postponed_chat_read_inbox_updates_.empty()) {
LOG(INFO) << "Send postponed chat read inbox updates";
auto dialog_ids = std::move(postponed_chat_read_inbox_updates_);
for (auto dialog_id : dialog_ids) {
send_update_chat_read_inbox(get_dialog(dialog_id), false, "after_get_difference");
}
}
while (!postponed_unread_message_count_updates_.empty()) {
auto *list = get_dialog_list(*postponed_unread_message_count_updates_.begin());
CHECK(list != nullptr);
send_update_unread_message_count(*list, DialogId(), true, "after_get_difference");
}
while (!postponed_unread_chat_count_updates_.empty()) {
auto *list = get_dialog_list(*postponed_unread_chat_count_updates_.begin());
CHECK(list != nullptr);
send_update_unread_chat_count(*list, DialogId(), true, "after_get_difference");
}
vector<FullMessageId> update_message_ids_to_delete;
for (auto &it : update_message_ids_) {
// there can be unhandled updateMessageId updates after getDifference even for ordinary chats,
// because despite updates coming during getDifference have already been applied,
// some of them could be postponed because of pts gap
auto full_message_id = it.first;
auto dialog_id = full_message_id.get_dialog_id();
auto message_id = full_message_id.get_message_id();
auto old_message_id = it.second;
CHECK(message_id.is_valid());
CHECK(message_id.is_server());
switch (dialog_id.get_type()) {
case DialogType::Channel:
// get channel difference may prevent updates from being applied
if (running_get_channel_difference(dialog_id)) {
break;
}
// fallthrough
case DialogType::User:
case DialogType::Chat: {
if (!have_message_force({dialog_id, old_message_id}, "after get difference")) {
// The sent message has already been deleted by the user or sent to inaccessible channel.
// The sent message may never be received, but we will need updateMessageId in case the message is received
// to delete it from the server and not add to the chat.
// But if the chat is inaccessible or the message is in an inaccessible chat part, then we will not be able to
// add the message or delete it from the server. In this case we forget updateMessageId for such messages in
// order to not check them over and over.
const Dialog *d = get_dialog(dialog_id);
if (!have_input_peer(dialog_id, AccessRights::Read) ||
(d != nullptr &&
message_id <= td::max(d->last_clear_history_message_id, d->max_unavailable_message_id))) {
update_message_ids_to_delete.push_back(full_message_id);
}
break;
}
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (dialog_id.get_type() == DialogType::Channel || message_id <= d->last_new_message_id) {
LOG(ERROR) << "Receive updateMessageId from " << old_message_id << " to " << full_message_id
<< " but not receive corresponding message, last_new_message_id = " << d->last_new_message_id;
}
if (dialog_id.get_type() != DialogType::Channel && message_id <= d->last_new_message_id) {
dump_debug_message_op(get_dialog(dialog_id));
}
if (message_id <= d->last_new_message_id) {
get_message_from_server(
full_message_id,
PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, old_message_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::on_restore_missing_message_after_get_difference,
full_message_id, old_message_id, std::move(result));
}),
"get missing");
} else if (dialog_id.get_type() == DialogType::Channel) {
LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
break;
}
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
break;
}
}
for (const auto &full_message_id : update_message_ids_to_delete) {
update_message_ids_.erase(full_message_id);
}
if (!td_->auth_manager_->is_bot()) {
if (!G()->td_db()->get_binlog_pmc()->isset("fetched_marks_as_unread")) {
td_->create_handler<GetDialogUnreadMarksQuery>()->send();
}
auto dialog_list_id = DialogListId(FolderId::archive());
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
if (!list->is_dialog_unread_count_inited_) {
int32 limit = list->are_pinned_dialogs_inited_ ? static_cast<int32>(list->pinned_dialogs_.size())
: get_pinned_dialogs_limit(dialog_list_id);
LOG(INFO) << "Loading chat list in " << dialog_list_id << " to init total unread count";
get_dialogs_from_list(dialog_list_id, limit + 2, Auto());
}
}
}
void MessagesManager::on_restore_missing_message_after_get_difference(FullMessageId full_message_id,
MessageId old_message_id, Result<Unit> result) {
if (result.is_error()) {
LOG(WARNING) << "Failed to get missing " << full_message_id << " for " << old_message_id << ": " << result.error();
} else {
LOG(WARNING) << "Successfully get missing " << full_message_id << " for " << old_message_id;
bool have_message = have_message_force(full_message_id, "on_restore_missing_message_after_get_difference");
if (!have_message && update_message_ids_.count(full_message_id)) {
LOG(ERROR) << "Receive messageEmpty instead of missing " << full_message_id << " for " << old_message_id;
delete_dialog_messages(full_message_id.get_dialog_id(), {old_message_id}, false,
"on_restore_missing_message_after_get_difference");
update_message_ids_.erase(full_message_id);
}
}
}
void MessagesManager::on_get_empty_messages(DialogId dialog_id, const vector<MessageId> &empty_message_ids) {
if (!empty_message_ids.empty()) {
delete_dialog_messages(dialog_id, empty_message_ids, false, "on_get_empty_messages");
}
}
void MessagesManager::get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info,
Promise<MessagesInfo> &&promise) {
if (!dialog_id.is_valid()) {
return get_channel_differences_if_needed(std::move(messages_info), std::move(promise));
}
for (auto &message : messages_info.messages) {
if (need_channel_difference_to_add_message(dialog_id, message)) {
return run_after_channel_difference(
dialog_id,
PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)](
Unit ignored) mutable { promise.set_value(std::move(messages_info)); }));
}
}
promise.set_value(std::move(messages_info));
}
void MessagesManager::get_channel_differences_if_needed(MessagesInfo &&messages_info, Promise<MessagesInfo> &&promise) {
MultiPromiseActorSafe mpas{"GetChannelDifferencesIfNeededMultiPromiseActor"};
mpas.add_promise(Promise<Unit>());
mpas.set_ignore_errors(true);
auto lock = mpas.get_promise();
for (auto &message : messages_info.messages) {
if (message == nullptr) {
continue;
}
auto dialog_id = DialogId::get_message_dialog_id(message);
if (need_channel_difference_to_add_message(dialog_id, message)) {
run_after_channel_difference(dialog_id, mpas.get_promise());
}
}
// must be added after messages_info is checked
mpas.add_promise(PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)](
Unit ignored) mutable { promise.set_value(std::move(messages_info)); }));
lock.set_value(Unit());
}
void MessagesManager::on_get_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages, bool is_channel_message,
bool is_scheduled, Promise<Unit> &&promise, const char *source) {
TRY_STATUS_PROMISE(promise, G()->close_status());
for (auto &message : messages) {
LOG(INFO) << "Receive " << to_string(message);
on_get_message(std::move(message), false, is_channel_message, is_scheduled, false, false, source);
}
promise.set_value(Unit());
}
bool MessagesManager::delete_newer_server_messages_at_the_end(Dialog *d, MessageId max_message_id) {
vector<MessageId> message_ids;
find_newer_messages(d->messages.get(), max_message_id, message_ids);
if (message_ids.empty()) {
return false;
}
vector<MessageId> server_message_ids;
vector<MessageId> kept_message_ids;
for (auto message_id : message_ids) {
CHECK(message_id > max_message_id);
if (message_id.is_server()) {
server_message_ids.push_back(message_id);
} else {
kept_message_ids.push_back(message_id);
}
}
delete_dialog_messages(d, server_message_ids, false, "delete_newer_server_messages_at_the_end");
// connect all messages with ID > max_message_id
for (size_t i = 0; i + 1 < kept_message_ids.size(); i++) {
auto m = get_message(d, kept_message_ids[i]);
CHECK(m != nullptr);
if (!m->have_next) {
m->have_next = true;
attach_message_to_next(d, kept_message_ids[i], "delete_newer_server_messages_at_the_end");
}
}
return !kept_message_ids.empty();
}
void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id,
int32 offset, int32 limit, bool from_the_end,
vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << messages.size() << " history messages " << (from_the_end ? "from the end " : "") << "in "
<< dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit;
CHECK(-limit < offset && offset <= 0);
CHECK(offset < 0 || from_the_end);
CHECK(!from_message_id.is_scheduled());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
MessageId last_received_message_id = messages.empty() ? MessageId() : MessageId::get_message_id(messages[0], false);
if (old_last_new_message_id < d->last_new_message_id && (from_the_end || old_last_new_message_id < from_message_id) &&
last_received_message_id < d->last_new_message_id) {
// new server messages were added to the dialog since the request was sent, but weren't received
// they should have been received, so we must repeat the request to get them
if (from_the_end) {
get_history_from_the_end_impl(d, false, false, std::move(promise), "on_get_history");
} else {
get_history_impl(d, from_message_id, offset, limit, false, false, std::move(promise));
}
return;
}
// the server can return less messages than requested if some of messages are deleted during request
// but if it happens, it is likely that there are no more messages on the server
bool have_full_history = from_the_end && narrow_cast<int32>(messages.size()) < limit && messages.size() <= 1;
if (messages.empty()) {
if (have_full_history) {
d->have_full_history = true;
d->have_full_history_source = 1;
on_dialog_updated(dialog_id, "set have_full_history");
}
if (from_the_end && d->have_full_history && d->messages == nullptr) {
if (!d->last_database_message_id.is_valid()) {
set_dialog_is_empty(d, "on_get_history empty");
} else {
LOG(INFO) << "Skip marking " << dialog_id << " as empty, because it probably has messages from "
<< d->first_database_message_id << " to " << d->last_database_message_id << " in the database";
}
}
// be aware that in some cases an empty answer may be returned, because of the race of getHistory and deleteMessages
// and not because there are no more messages
promise.set_value(Unit());
return;
}
if (messages.size() > 1) {
// check that messages are received in decreasing message_id order
MessageId cur_message_id = MessageId::max();
for (const auto &message : messages) {
MessageId message_id = MessageId::get_message_id(message, false);
if (message_id >= cur_message_id) {
string error = PSTRING() << "Receive messages in the wrong order in history of " << dialog_id << " from "
<< from_message_id << " with offset " << offset << ", limit " << limit
<< ", from_the_end = " << from_the_end << ": ";
for (const auto &debug_message : messages) {
error += to_string(debug_message);
}
LOG(ERROR) << error;
promise.set_value(Unit());
return;
}
cur_message_id = message_id;
}
}
// be aware that returned messages are guaranteed to be consecutive messages, but if !from_the_end they
// may be older (if some messages was deleted) or newer (if some messages were added) than an expected answer
// be aware that any subset of the returned messages may be already deleted and returned as MessageEmpty
bool is_channel_message = dialog_id.get_type() == DialogType::Channel;
MessageId first_added_message_id;
MessageId last_added_message_id;
bool have_next = false;
if (narrow_cast<int32>(messages.size()) < limit + offset && messages.size() <= 1) {
MessageId first_received_message_id = MessageId::get_message_id(messages.back(), false);
if (first_received_message_id >= from_message_id && d->first_database_message_id.is_valid() &&
first_received_message_id >= d->first_database_message_id) {
// it is likely that there are no more history messages on the server
have_full_history = true;
}
}
if (d->last_new_message_id.is_valid()) {
// remove too new messages from response
while (!messages.empty()) {
if (DialogId::get_message_dialog_id(messages[0]) == dialog_id &&
MessageId::get_message_id(messages[0], false) <= d->last_new_message_id) {
// the message is old enough
break;
}
LOG(INFO) << "Ignore too new " << MessageId::get_message_id(messages[0], false);
messages.erase(messages.begin());
if (messages.empty()) {
// received no suitable messages; try again
return promise.set_value(Unit());
} else {
last_received_message_id = MessageId::get_message_id(messages[0], false);
}
}
}
bool prev_have_full_history = d->have_full_history;
MessageId prev_last_new_message_id = d->last_new_message_id;
MessageId prev_first_database_message_id = d->first_database_message_id;
MessageId prev_last_database_message_id = d->last_database_message_id;
MessageId prev_last_message_id = d->last_message_id;
if (from_the_end) {
// delete all server messages with ID > last_received_message_id
// there were no new messages received after the getHistory request was sent, so they are already deleted message
if (delete_newer_server_messages_at_the_end(d, last_received_message_id)) {
have_next = true;
}
}
for (auto &message : messages) {
auto expected_message_id = MessageId::get_message_id(message, false);
if (!have_next && from_the_end && expected_message_id < d->last_message_id) {
// last message in the dialog should be attached to the next message if there is some
have_next = true;
}
auto message_dialog_id = DialogId::get_message_dialog_id(message);
if (message_dialog_id != dialog_id) {
LOG(ERROR) << "Receive " << expected_message_id << " in wrong " << message_dialog_id << " instead of "
<< dialog_id << ": " << oneline(to_string(message));
continue;
}
auto full_message_id =
on_get_message(std::move(message), false, is_channel_message, false, false, have_next, "get history");
auto message_id = full_message_id.get_message_id();
if (message_id.is_valid()) {
CHECK(message_id == expected_message_id);
if (!last_added_message_id.is_valid()) {
last_added_message_id = message_id;
}
if (!have_next) {
have_next = true;
} else if (first_added_message_id.is_valid()) {
Message *next_message = get_message(d, first_added_message_id);
CHECK(next_message != nullptr);
if (!next_message->have_previous) {
LOG(INFO) << "Fix have_previous for " << first_added_message_id;
next_message->have_previous = true;
attach_message_to_previous(d, first_added_message_id, "on_get_history");
}
}
first_added_message_id = message_id;
}
}
if (from_the_end && last_added_message_id.is_valid() && last_added_message_id != last_received_message_id) {
CHECK(last_added_message_id < last_received_message_id);
delete_newer_server_messages_at_the_end(d, last_added_message_id);
}
if (have_full_history) {
d->have_full_history = true;
d->have_full_history_source = 2;
on_dialog_updated(dialog_id, "set have_full_history 2");
}
// LOG_IF(ERROR, d->first_message_id.is_valid() && d->first_message_id > first_received_message_id)
// << "Receive " << first_received_message_id << ", but first chat message is " << d->first_message_id;
if (from_the_end && !d->last_new_message_id.is_valid()) {
set_dialog_last_new_message_id(
d, last_added_message_id.is_valid() ? last_added_message_id : last_received_message_id, "on_get_history");
}
bool intersect_last_database_message_ids =
last_added_message_id >= d->first_database_message_id && d->last_database_message_id >= first_added_message_id;
bool need_update_database_message_ids =
last_added_message_id.is_valid() && (from_the_end || intersect_last_database_message_ids);
if (from_the_end && last_added_message_id.is_valid() && last_added_message_id > d->last_message_id) {
CHECK(d->last_new_message_id.is_valid());
set_dialog_last_message_id(d, last_added_message_id, "on_get_history");
send_update_chat_last_message(d, "on_get_history");
}
if (need_update_database_message_ids) {
if (from_the_end && !intersect_last_database_message_ids && d->last_database_message_id.is_valid()) {
if (d->last_database_message_id < first_added_message_id || last_added_message_id == d->last_message_id) {
set_dialog_first_database_message_id(d, MessageId(), "on_get_history 1");
set_dialog_last_database_message_id(d, MessageId(), "on_get_history 1");
} else {
auto min_message_id = td::min(d->first_database_message_id, d->last_message_id);
LOG_CHECK(last_added_message_id < min_message_id)
<< need_update_database_message_ids << ' ' << first_added_message_id << ' ' << last_added_message_id << ' '
<< d->first_database_message_id << ' ' << d->last_database_message_id << ' ' << d->last_new_message_id
<< ' ' << d->last_message_id << ' ' << prev_first_database_message_id << ' '
<< prev_last_database_message_id << ' ' << prev_last_new_message_id << ' ' << prev_last_message_id;
if (min_message_id <= last_added_message_id.get_next_message_id(MessageType::Server)) {
// connect local messages with last received server message
set_dialog_first_database_message_id(d, last_added_message_id, "on_get_history 2");
} else {
LOG(WARNING) << "Have last " << d->last_message_id << " and first database " << d->first_database_message_id
<< " in " << dialog_id << ", but received history from the end only up to "
<< last_added_message_id;
// can't connect messages, because there can be unknown server messages after last_added_message_id
}
}
}
if (!d->last_database_message_id.is_valid()) {
CHECK(d->last_message_id.is_valid());
MessagesConstIterator it(d, d->last_message_id);
MessageId new_first_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
if (message_id.is_server() || message_id.is_local()) {
if (!d->last_database_message_id.is_valid()) {
set_dialog_last_database_message_id(d, message_id, "on_get_history");
}
new_first_database_message_id = message_id;
try_restore_dialog_reply_markup(d, *it);
}
--it;
}
if (new_first_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history");
}
} else {
LOG_CHECK(d->last_new_message_id.is_valid())
<< dialog_id << " " << from_the_end << " " << d->first_database_message_id << " "
<< d->last_database_message_id << " " << first_added_message_id << " " << last_added_message_id << " "
<< d->last_message_id << " " << d->last_new_message_id << " " << d->have_full_history << " "
<< prev_last_new_message_id << " " << prev_first_database_message_id << " " << prev_last_database_message_id
<< " " << prev_last_message_id << " " << prev_have_full_history << " " << d->debug_last_new_message_id << " "
<< d->debug_first_database_message_id << " " << d->debug_last_database_message_id << " " << from_message_id
<< " " << offset << " " << limit << " " << messages.size() << " " << last_received_message_id << " "
<< d->debug_set_dialog_last_database_message_id;
CHECK(d->first_database_message_id.is_valid());
{
MessagesConstIterator it(d, d->first_database_message_id);
if (*it != nullptr && ((*it)->message_id == d->first_database_message_id || (*it)->have_next)) {
MessageId new_first_database_message_id = d->first_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
if ((message_id.is_server() || message_id.is_local()) && message_id < new_first_database_message_id) {
new_first_database_message_id = message_id;
try_restore_dialog_reply_markup(d, *it);
}
--it;
}
if (new_first_database_message_id != d->first_database_message_id) {
set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history 2");
}
}
}
{
MessagesConstIterator it(d, d->last_database_message_id);
if (*it != nullptr && ((*it)->message_id == d->last_database_message_id || (*it)->have_next)) {
MessageId new_last_database_message_id = d->last_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
if ((message_id.is_server() || message_id.is_local()) && message_id > new_last_database_message_id) {
new_last_database_message_id = message_id;
}
++it;
}
if (new_last_database_message_id != d->last_database_message_id) {
set_dialog_last_database_message_id(d, new_last_database_message_id, "on_get_history 2");
}
}
}
}
LOG_CHECK(d->first_database_message_id.is_valid())
<< dialog_id << " " << from_the_end << " " << d->first_database_message_id << " " << d->last_database_message_id
<< " " << first_added_message_id << " " << last_added_message_id << " " << d->last_message_id << " "
<< d->last_new_message_id << " " << d->have_full_history << " " << prev_last_new_message_id << " "
<< prev_first_database_message_id << " " << prev_last_database_message_id << " " << prev_last_message_id << " "
<< prev_have_full_history << " " << d->debug_last_new_message_id << " " << d->debug_first_database_message_id
<< " " << d->debug_last_database_message_id << " " << from_message_id << " " << offset << " " << limit << " "
<< messages.size() << " " << last_received_message_id << " " << d->debug_set_dialog_last_database_message_id;
CHECK(d->last_database_message_id.is_valid());
for (auto &first_message_id : d->first_database_message_id_by_index) {
if (first_added_message_id < first_message_id && first_message_id <= last_added_message_id) {
first_message_id = first_added_message_id;
}
}
}
promise.set_value(Unit());
}
vector<DialogId> MessagesManager::get_peers_dialog_ids(vector<tl_object_ptr<telegram_api::Peer>> &&peers) {
vector<DialogId> result;
result.reserve(peers.size());
for (auto &peer : peers) {
DialogId dialog_id(peer);
if (dialog_id.is_valid()) {
force_create_dialog(dialog_id, "get_peers_dialog_ids");
result.push_back(dialog_id);
}
}
return result;
}
void MessagesManager::on_get_public_dialogs_search_result(const string &query,
vector<tl_object_ptr<telegram_api::Peer>> &&my_peers,
vector<tl_object_ptr<telegram_api::Peer>> &&peers) {
auto it = search_public_dialogs_queries_.find(query);
CHECK(it != search_public_dialogs_queries_.end());
CHECK(!it->second.empty());
auto promises = std::move(it->second);
search_public_dialogs_queries_.erase(it);
found_public_dialogs_[query] = get_peers_dialog_ids(std::move(peers));
found_on_server_dialogs_[query] = get_peers_dialog_ids(std::move(my_peers));
set_promises(promises);
}
void MessagesManager::on_failed_public_dialogs_search(const string &query, Status &&error) {
auto it = search_public_dialogs_queries_.find(query);
CHECK(it != search_public_dialogs_queries_.end());
CHECK(!it->second.empty());
auto promises = std::move(it->second);
search_public_dialogs_queries_.erase(it);
found_public_dialogs_[query]; // negative cache
found_on_server_dialogs_[query]; // negative cache
fail_promises(promises, std::move(error));
}
void MessagesManager::on_get_message_search_result_calendar(
DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id, int32 total_count,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
vector<tl_object_ptr<telegram_api::searchResultsCalendarPeriod>> &&periods, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
auto it = found_dialog_message_calendars_.find(random_id);
CHECK(it != found_dialog_message_calendars_.end());
int32 received_message_count = 0;
for (auto &message : messages) {
auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
false, false, false, "on_get_message_search_result_calendar");
if (new_full_message_id == FullMessageId()) {
total_count--;
continue;
}
if (new_full_message_id.get_dialog_id() != dialog_id) {
LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
total_count--;
continue;
}
received_message_count++;
}
if (total_count < received_message_count) {
LOG(ERROR) << "Receive " << received_message_count << " valid messages out of " << total_count << " in "
<< messages.size() << " messages";
total_count = received_message_count;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
if (old_message_count != total_count) {
old_message_count = total_count;
on_dialog_updated(dialog_id, "on_get_message_search_result_calendar");
}
vector<td_api::object_ptr<td_api::messageCalendarDay>> days;
for (auto &period : periods) {
auto message_id = MessageId(ServerMessageId(period->min_msg_id_));
const auto *m = get_message(d, message_id);
if (m == nullptr) {
LOG(ERROR) << "Failed to find " << message_id;
continue;
}
if (period->count_ <= 0) {
LOG(ERROR) << "Receive " << to_string(period);
continue;
}
days.push_back(td_api::make_object<td_api::messageCalendarDay>(
period->count_, get_message_object(dialog_id, m, "on_get_message_search_result_calendar")));
}
it->second = td_api::make_object<td_api::messageCalendar>(total_count, std::move(days));
promise.set_value(Unit());
}
void MessagesManager::on_failed_get_message_search_result_calendar(DialogId dialog_id, int64 random_id) {
auto it = found_dialog_message_calendars_.find(random_id);
CHECK(it != found_dialog_message_calendars_.end());
found_dialog_message_calendars_.erase(it);
}
void MessagesManager::on_get_dialog_messages_search_result(
DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset,
int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id, int32 total_count,
vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id;
if (!dialog_id.is_valid()) {
CHECK(query.empty());
CHECK(!sender_dialog_id.is_valid());
CHECK(!top_thread_message_id.is_valid());
auto it = found_call_messages_.find(random_id);
CHECK(it != found_call_messages_.end());
MessageId first_added_message_id;
if (messages.empty()) {
// messages may be empty because there are no more messages or they can't be found due to global limit
// anyway pretend that there are no more messages
first_added_message_id = MessageId::min();
}
auto &result = it->second.second;
CHECK(result.empty());
int32 added_message_count = 0;
for (auto &message : messages) {
auto new_full_message_id =
on_get_message(std::move(message), false, false, false, false, false, "search call messages");
if (new_full_message_id == FullMessageId()) {
continue;
}
result.push_back(new_full_message_id);
added_message_count++;
auto message_id = new_full_message_id.get_message_id();
CHECK(message_id.is_valid());
if (message_id < first_added_message_id || !first_added_message_id.is_valid()) {
first_added_message_id = message_id;
}
}
if (total_count < added_message_count) {
LOG(ERROR) << "Receive total_count = " << total_count << ", but added " << added_message_count
<< " messages out of " << messages.size();
total_count = added_message_count;
}
if (G()->parameters().use_message_db) {
bool update_state = false;
auto &old_message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
if (old_message_count != total_count) {
LOG(INFO) << "Update calls database message count to " << total_count;
old_message_count = total_count;
update_state = true;
}
auto &old_first_db_message_id =
calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)];
bool from_the_end = !from_message_id.is_valid() || from_message_id >= MessageId::max();
LOG(INFO) << "Have from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id
<< ", first_added_message_id = " << first_added_message_id << ", from_message_id = " << from_message_id;
if ((from_the_end || (old_first_db_message_id.is_valid() && old_first_db_message_id <= from_message_id)) &&
(!old_first_db_message_id.is_valid() || first_added_message_id < old_first_db_message_id)) {
LOG(INFO) << "Update calls database first message to " << first_added_message_id;
old_first_db_message_id = first_added_message_id;
update_state = true;
}
if (update_state) {
save_calls_db_state();
}
}
it->second.first = total_count;
promise.set_value(Unit());
return;
}
auto it = found_dialog_messages_.find(random_id);
CHECK(it != found_dialog_messages_.end());
auto &result = it->second.second;
CHECK(result.empty());
MessageId first_added_message_id;
if (messages.empty()) {
// messages may be empty because there are no more messages or they can't be found due to global limit
// anyway pretend that there are no more messages
first_added_message_id = MessageId::min();
}
bool can_be_in_different_dialog = top_thread_message_id.is_valid() && is_broadcast_channel(dialog_id);
DialogId real_dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto &message : messages) {
auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
false, false, false, "on_get_dialog_messages_search_result");
if (new_full_message_id == FullMessageId()) {
total_count--;
continue;
}
if (new_full_message_id.get_dialog_id() != dialog_id) {
if (!can_be_in_different_dialog) {
LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
total_count--;
continue;
} else {
if (!real_dialog_id.is_valid()) {
real_dialog_id = new_full_message_id.get_dialog_id();
found_dialog_messages_dialog_id_[random_id] = real_dialog_id;
} else if (new_full_message_id.get_dialog_id() != real_dialog_id) {
LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << real_dialog_id << " or "
<< dialog_id;
total_count--;
continue;
}
}
}
auto message_id = new_full_message_id.get_message_id();
if (filter == MessageSearchFilter::UnreadMention && message_id <= d->last_read_all_mentions_message_id &&
!real_dialog_id.is_valid()) {
total_count--;
continue;
}
// TODO check that messages are returned in decreasing message_id order
if (message_id < first_added_message_id || !first_added_message_id.is_valid()) {
first_added_message_id = message_id;
}
result.push_back(message_id);
}
if (total_count < static_cast<int32>(result.size())) {
LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
<< " messages";
total_count = static_cast<int32>(result.size());
}
if (query.empty() && !sender_dialog_id.is_valid() && filter != MessageSearchFilter::Empty &&
!top_thread_message_id.is_valid()) {
bool from_the_end = !from_message_id.is_valid() ||
(d->last_message_id != MessageId() && from_message_id > d->last_message_id) ||
from_message_id >= MessageId::max();
bool update_dialog = false;
auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
if (old_message_count != total_count) {
old_message_count = total_count;
if (filter == MessageSearchFilter::UnreadMention) {
d->unread_mention_count = old_message_count;
update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
if (filter == MessageSearchFilter::UnreadReaction) {
d->unread_reaction_count = old_message_count;
// update_dialog_mention_notification_count(d);
send_update_chat_unread_reaction_count(d, "on_get_dialog_messages_search_result");
}
update_dialog = true;
}
auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)];
if ((from_the_end ||
(old_first_database_message_id.is_valid() && old_first_database_message_id <= from_message_id)) &&
(!old_first_database_message_id.is_valid() || first_added_message_id < old_first_database_message_id)) {
old_first_database_message_id = first_added_message_id;
update_dialog = true;
}
if (update_dialog) {
on_dialog_updated(dialog_id, "search results");
}
if (from_the_end && filter == MessageSearchFilter::Pinned) {
set_dialog_last_pinned_message_id(d, result.empty() ? MessageId() : result[0]);
}
}
it->second.first = total_count;
promise.set_value(Unit());
}
void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id) {
if (!dialog_id.is_valid()) {
auto it = found_call_messages_.find(random_id);
CHECK(it != found_call_messages_.end());
found_call_messages_.erase(it);
return;
}
auto it = found_dialog_messages_.find(random_id);
CHECK(it != found_dialog_messages_.end());
found_dialog_messages_.erase(it);
}
void MessagesManager::on_get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, int32 total_count,
Promise<int32> &&promise) {
LOG(INFO) << "Receive " << total_count << " message count in " << dialog_id << " with filter " << filter;
if (total_count < 0) {
LOG(ERROR) << "Receive total message count = " << total_count << " in " << dialog_id << " with filter " << filter;
total_count = 0;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
CHECK(filter != MessageSearchFilter::Empty);
CHECK(filter != MessageSearchFilter::UnreadMention);
CHECK(filter != MessageSearchFilter::UnreadReaction);
CHECK(filter != MessageSearchFilter::FailedToSend);
auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
if (old_message_count != total_count) {
old_message_count = total_count;
on_dialog_updated(dialog_id, "on_get_dialog_message_count");
}
if (total_count == 0) {
auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)];
if (old_first_database_message_id != MessageId::min()) {
old_first_database_message_id = MessageId::min();
on_dialog_updated(dialog_id, "on_get_dialog_message_count");
}
if (filter == MessageSearchFilter::Pinned) {
set_dialog_last_pinned_message_id(d, MessageId());
}
}
promise.set_value(std::move(total_count));
}
void MessagesManager::on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id,
MessageId offset_message_id, int32 limit,
MessageSearchFilter filter, int32 min_date, int32 max_date,
int64 random_id, int32 total_count,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
int32 next_rate, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << messages.size() << " found messages";
auto it = found_messages_.find(random_id);
CHECK(it != found_messages_.end());
auto &result = it->second.full_message_ids;
CHECK(result.empty());
int32 last_message_date = 0;
MessageId last_message_id;
DialogId last_dialog_id;
for (auto &message : messages) {
auto message_date = get_message_date(message);
auto message_id = MessageId::get_message_id(message, false);
auto dialog_id = DialogId::get_message_dialog_id(message);
if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) {
last_message_date = message_date;
last_message_id = message_id;
last_dialog_id = dialog_id;
}
auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
false, false, false, "search messages");
if (new_full_message_id != FullMessageId()) {
CHECK(dialog_id == new_full_message_id.get_dialog_id());
result.push_back(new_full_message_id);
} else {
total_count--;
}
}
if (total_count < static_cast<int32>(result.size())) {
LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
<< " messages";
total_count = static_cast<int32>(result.size());
}
it->second.total_count = total_count;
if (!result.empty()) {
if (next_rate > 0) {
last_message_date = next_rate;
}
it->second.next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ','
<< last_message_id.get_server_message_id().get();
}
promise.set_value(Unit());
}
void MessagesManager::on_failed_messages_search(int64 random_id) {
auto it = found_messages_.find(random_id);
CHECK(it != found_messages_.end());
found_messages_.erase(it);
}
void MessagesManager::on_get_outgoing_document_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
FoundMessages found_messages;
for (auto &message : messages) {
auto dialog_id = DialogId::get_message_dialog_id(message);
auto full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false,
false, false, "on_get_outgoing_document_messages");
if (full_message_id != FullMessageId()) {
CHECK(dialog_id == full_message_id.get_dialog_id());
found_messages.full_message_ids.push_back(full_message_id);
}
}
auto result = get_found_messages_object(found_messages, "on_get_outgoing_document_messages");
td::remove_if(result->messages_,
[](const auto &message) { return message->content_->get_id() != td_api::messageDocument::ID; });
result->total_count_ = narrow_cast<int32>(result->messages_.size());
promise.set_value(std::move(result));
}
void MessagesManager::on_get_scheduled_server_messages(DialogId dialog_id, uint32 generation,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
bool is_not_modified) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (generation < d->scheduled_messages_sync_generation) {
LOG(INFO) << "Ignore scheduled messages with old generation " << generation << " instead of "
<< d->scheduled_messages_sync_generation << " in " << dialog_id;
return;
}
d->scheduled_messages_sync_generation = generation;
if (is_not_modified) {
LOG(INFO) << "Scheduled messages are mot modified in " << dialog_id;
return;
}
vector<MessageId> old_message_ids;
find_old_messages(d->scheduled_messages.get(),
MessageId(ScheduledServerMessageId(), std::numeric_limits<int32>::max(), true), old_message_ids);
FlatHashMap<ScheduledServerMessageId, MessageId, ScheduledServerMessageIdHash> old_server_message_ids;
for (auto &message_id : old_message_ids) {
if (message_id.is_scheduled_server()) {
old_server_message_ids[message_id.get_scheduled_server_message_id()] = message_id;
}
}
bool is_channel_message = dialog_id.get_type() == DialogType::Channel;
bool has_scheduled_server_messages = false;
for (auto &message : messages) {
auto message_dialog_id = DialogId::get_message_dialog_id(message);
if (message_dialog_id != dialog_id) {
LOG(ERROR) << "Receive " << MessageId::get_message_id(message, true) << " in wrong " << message_dialog_id
<< " instead of " << dialog_id << ": " << oneline(to_string(message));
continue;
}
auto full_message_id = on_get_message(std::move(message), d->sent_scheduled_messages, is_channel_message, true,
false, false, "on_get_scheduled_server_messages");
auto message_id = full_message_id.get_message_id();
if (message_id.is_valid_scheduled()) {
CHECK(message_id.is_scheduled_server());
old_server_message_ids.erase(message_id.get_scheduled_server_message_id());
has_scheduled_server_messages = true;
}
}
on_update_dialog_has_scheduled_server_messages(dialog_id, has_scheduled_server_messages);
for (const auto &it : old_server_message_ids) {
auto message_id = it.second;
auto message = do_delete_scheduled_message(d, message_id, true, "on_get_scheduled_server_messages");
CHECK(message != nullptr);
send_update_delete_messages(dialog_id, {message->message_id.get()}, true);
}
send_update_chat_has_scheduled_messages(d, false);
}
void MessagesManager::on_get_recent_locations(DialogId dialog_id, int32 limit, int32 total_count,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
Promise<td_api::object_ptr<td_api::messages>> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << messages.size() << " recent locations in " << dialog_id;
vector<MessageId> result;
for (auto &message : messages) {
auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
false, false, false, "get recent locations");
if (new_full_message_id != FullMessageId()) {
if (new_full_message_id.get_dialog_id() != dialog_id) {
LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
total_count--;
continue;
}
auto m = get_message(new_full_message_id);
CHECK(m != nullptr);
if (m->content->get_type() != MessageContentType::LiveLocation) {
LOG(ERROR) << "Receive a message of wrong type " << m->content->get_type() << " in on_get_recent_locations in "
<< dialog_id;
total_count--;
continue;
}
result.push_back(m->message_id);
} else {
total_count--;
}
}
if (total_count < static_cast<int32>(result.size())) {
LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
<< " messages";
total_count = static_cast<int32>(result.size());
}
promise.set_value(get_messages_object(total_count, dialog_id, result, true, "on_get_recent_locations"));
}
void MessagesManager::on_get_message_public_forwards(int32 total_count,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
int32 next_rate,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << messages.size() << " forwarded messages";
vector<td_api::object_ptr<td_api::message>> result;
int32 last_message_date = 0;
MessageId last_message_id;
DialogId last_dialog_id;
for (auto &message : messages) {
auto message_date = get_message_date(message);
auto message_id = MessageId::get_message_id(message, false);
auto dialog_id = DialogId::get_message_dialog_id(message);
if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) {
last_message_date = message_date;
last_message_id = message_id;
last_dialog_id = dialog_id;
}
auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
false, false, false, "get message public forwards");
if (new_full_message_id != FullMessageId()) {
CHECK(dialog_id == new_full_message_id.get_dialog_id());
result.push_back(get_message_object(new_full_message_id, "on_get_message_public_forwards"));
CHECK(result.back() != nullptr);
} else {
total_count--;
}
}
if (total_count < static_cast<int32>(result.size())) {
LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
<< " messages";
total_count = static_cast<int32>(result.size());
}
string next_offset;
if (!result.empty()) {
if (next_rate > 0) {
last_message_date = next_rate;
}
next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ','
<< last_message_id.get_server_message_id().get();
}
promise.set_value(td_api::make_object<td_api::foundMessages>(total_count, std::move(result), next_offset));
}
void MessagesManager::delete_messages_from_updates(const vector<MessageId> &message_ids) {
FlatHashMap<DialogId, vector<int64>, DialogIdHash> deleted_message_ids;
FlatHashMap<DialogId, bool, DialogIdHash> need_update_dialog_pos;
vector<unique_ptr<Message>> deleted_messages;
for (auto message_id : message_ids) {
if (!message_id.is_valid() || !message_id.is_server()) {
LOG(ERROR) << "Incoming update tries to delete " << message_id;
continue;
}
Dialog *d = get_dialog_by_message_id(message_id);
if (d != nullptr) {
auto message =
delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates");
CHECK(message != nullptr);
LOG_CHECK(message->message_id == message_id) << message_id << " " << message->message_id << " " << d->dialog_id;
deleted_message_ids[d->dialog_id].push_back(message->message_id.get());
deleted_messages.push_back(std::move(message));
}
if (last_clear_history_message_id_to_dialog_id_.count(message_id)) {
d = get_dialog(last_clear_history_message_id_to_dialog_id_[message_id]);
CHECK(d != nullptr);
auto message =
delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates");
CHECK(message == nullptr);
}
}
if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages);
}
for (auto &it : need_update_dialog_pos) {
if (it.second) {
auto dialog_id = it.first;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
send_update_chat_last_message(d, "delete_messages_from_updates");
}
}
for (auto &it : deleted_message_ids) {
auto dialog_id = it.first;
send_update_delete_messages(dialog_id, std::move(it.second), true);
}
}
void MessagesManager::delete_dialog_messages(DialogId dialog_id, const vector<MessageId> &message_ids,
bool force_update_for_not_found_messages, const char *source) {
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages");
if (d == nullptr) {
LOG(INFO) << "Ignore deleteChannelMessages for unknown " << dialog_id << " from " << source;
CHECK(dialog_id.get_type() == DialogType::Channel);
return;
}
delete_dialog_messages(d, message_ids, force_update_for_not_found_messages, source);
}
void MessagesManager::delete_dialog_messages(Dialog *d, const vector<MessageId> &message_ids,
bool force_update_for_not_found_messages, const char *source) {
vector<unique_ptr<Message>> deleted_messages;
vector<int64> deleted_message_ids;
bool need_update_dialog_pos = false;
bool need_update_chat_has_scheduled_messages = false;
for (auto message_id : message_ids) {
CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
bool force_update = force_update_for_not_found_messages && !is_deleted_message(d, message_id);
auto message = delete_message(d, message_id, true, &need_update_dialog_pos, source);
if (message == nullptr) {
if (force_update) {
deleted_message_ids.push_back(message_id.get());
}
} else {
need_update_chat_has_scheduled_messages |= message->message_id.is_scheduled();
deleted_message_ids.push_back(message->message_id.get());
deleted_messages.push_back(std::move(message));
}
}
if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages);
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, source);
}
send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), true);
if (need_update_chat_has_scheduled_messages) {
send_update_chat_has_scheduled_messages(d, true);
}
}
void MessagesManager::update_dialog_pinned_messages_from_updates(DialogId dialog_id,
const vector<MessageId> &message_ids, bool is_pin) {
Dialog *d = get_dialog_force(dialog_id, "update_dialog_pinned_messages_from_updates");
if (d == nullptr) {
LOG(INFO) << "Ignore updatePinnedMessages for unknown " << dialog_id;
return;
}
for (auto message_id : message_ids) {
if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) {
LOG(ERROR) << "Incoming update tries to pin/unpin " << message_id << " in " << dialog_id;
continue;
}
Message *m = get_message_force(d, message_id, "update_dialog_pinned_messages_from_updates");
if (m != nullptr && update_message_is_pinned(d, m, is_pin, "update_dialog_pinned_messages_from_updates")) {
on_message_changed(d, m, true, "update_dialog_pinned_messages_from_updates");
}
}
}
bool MessagesManager::update_message_is_pinned(Dialog *d, Message *m, bool is_pinned, const char *source) {
CHECK(m != nullptr);
CHECK(!m->message_id.is_scheduled());
if (m->is_pinned == is_pinned) {
return false;
}
LOG(INFO) << "Update message is_pinned of " << m->message_id << " in " << d->dialog_id << " to " << is_pinned
<< " from " << source;
auto old_index_mask = get_message_index_mask(d->dialog_id, m);
m->is_pinned = is_pinned;
auto new_index_mask = get_message_index_mask(d->dialog_id, m);
update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask);
update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask);
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageIsPinned>(d->dialog_id.get(), m->message_id.get(), is_pinned));
if (is_pinned) {
if (d->is_last_pinned_message_id_inited && m->message_id > d->last_pinned_message_id) {
set_dialog_last_pinned_message_id(d, m->message_id);
}
} else {
if (d->is_last_pinned_message_id_inited && m->message_id == d->last_pinned_message_id) {
if (d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] == 0) {
set_dialog_last_pinned_message_id(d, MessageId());
} else {
drop_dialog_last_pinned_message_id(d);
}
}
}
return true;
}
string MessagesManager::get_message_search_text(const Message *m) const {
if (m->is_content_secret) {
return string();
}
return get_message_content_search_text(td_, m->content.get());
}
bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message *m) {
if (m == nullptr) {
return false;
}
if (m->ttl > 0) {
return false;
}
if (m->message_id.is_scheduled()) {
return false;
}
switch (from_dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
// ok
break;
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
return can_forward_message_content(m->content.get());
}
bool MessagesManager::can_save_message(DialogId dialog_id, const Message *m) const {
if (m == nullptr || m->noforwards || m->is_content_secret) {
return false;
}
return !get_dialog_has_protected_content(dialog_id);
}
bool MessagesManager::can_get_message_statistics(FullMessageId full_message_id) {
return can_get_message_statistics(full_message_id.get_dialog_id(),
get_message_force(full_message_id, "can_get_message_statistics"));
}
bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Message *m) const {
if (td_->auth_manager_->is_bot()) {
return false;
}
if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 ||
m->had_forward_info || (m->forward_info != nullptr && m->forward_info->message_id.is_valid())) {
return false;
}
return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id);
}
bool MessagesManager::can_delete_channel_message(const DialogParticipantStatus &status, const Message *m, bool is_bot) {
if (m == nullptr) {
return true;
}
if (m->message_id.is_local() || m->message_id.is_yet_unsent()) {
return true;
}
if (m->message_id.is_scheduled()) {
if (m->is_channel_post) {
return status.can_post_messages();
}
return true;
}
if (is_bot && G()->unix_time_cached() >= m->date + 2 * 86400) {
// bots can't delete messages older than 2 days
return false;
}
CHECK(m->message_id.is_server());
if (m->message_id.get_server_message_id().get() == 1) {
return false;
}
auto content_type = m->content->get_type();
if (content_type == MessageContentType::ChannelMigrateFrom || content_type == MessageContentType::ChannelCreate ||
content_type == MessageContentType::TopicCreate) {
return false;
}
if (status.can_delete_messages()) {
return true;
}
if (!m->is_outgoing) {
return false;
}
if (m->is_channel_post || is_service_message_content(content_type)) {
return status.can_post_messages();
}
return true;
}
bool MessagesManager::can_delete_message(DialogId dialog_id, const Message *m) const {
if (m == nullptr) {
return true;
}
if (m->message_id.is_local() || m->message_id.is_yet_unsent()) {
return true;
}
switch (dialog_id.get_type()) {
case DialogType::User:
return true;
case DialogType::Chat:
return true;
case DialogType::Channel: {
auto dialog_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
return can_delete_channel_message(dialog_status, m, td_->auth_manager_->is_bot());
}
case DialogType::SecretChat:
return true;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
}
bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) const {
if (m == nullptr) {
return true;
}
if (m->message_id.is_local()) {
return false;
}
if (dialog_id == get_my_dialog_id()) {
return false;
}
if (m->message_id.is_scheduled()) {
return false;
}
if (m->message_id.is_yet_unsent()) {
return true;
}
CHECK(m->message_id.is_server());
const int32 DEFAULT_REVOKE_TIME_LIMIT = td_->auth_manager_->is_bot() ? 2 * 86400 : std::numeric_limits<int32>::max();
auto content_type = m->content->get_type();
switch (dialog_id.get_type()) {
case DialogType::User: {
bool can_revoke_incoming = td_->option_manager_->get_option_boolean("revoke_pm_inbox", true);
int64 revoke_time_limit =
td_->option_manager_->get_option_integer("revoke_pm_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
if (G()->unix_time_cached() - m->date < 86400 && content_type == MessageContentType::Dice) {
return false;
}
return ((m->is_outgoing && !is_service_message_content(content_type)) ||
(can_revoke_incoming && content_type != MessageContentType::ScreenshotTaken)) &&
G()->unix_time_cached() - m->date <= revoke_time_limit;
}
case DialogType::Chat: {
bool is_appointed_administrator =
td_->contacts_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id());
int64 revoke_time_limit =
td_->option_manager_->get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
return ((m->is_outgoing && !is_service_message_content(content_type)) || is_appointed_administrator) &&
G()->unix_time_cached() - m->date <= revoke_time_limit;
}
case DialogType::Channel:
return true; // any server message that can be deleted will be deleted for all participants
case DialogType::SecretChat:
// all non-service messages will be deleted for everyone if secret chat is active
return td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Active &&
!is_service_message_content(content_type);
case DialogType::None:
default:
UNREACHABLE();
return false;
}
}
void MessagesManager::delete_messages(DialogId dialog_id, const vector<MessageId> &input_message_ids, bool revoke,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
Dialog *d = get_dialog_force(dialog_id, "delete_messages");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat is not found"));
}
if (input_message_ids.empty()) {
return promise.set_value(Unit());
}
auto dialog_type = dialog_id.get_type();
bool is_secret = dialog_type == DialogType::SecretChat;
vector<MessageId> message_ids;
message_ids.reserve(input_message_ids.size());
vector<MessageId> deleted_server_message_ids;
vector<MessageId> deleted_scheduled_server_message_ids;
for (auto message_id : input_message_ids) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return promise.set_error(Status::Error(400, "Invalid message identifier"));
}
message_id = get_persistent_message_id(d, message_id);
message_ids.push_back(message_id);
auto m = get_message_force(d, message_id, "delete_messages");
if (m != nullptr) {
if (m->message_id.is_scheduled()) {
if (m->message_id.is_scheduled_server()) {
deleted_scheduled_server_message_ids.push_back(m->message_id);
}
} else {
if (m->message_id.is_server() || is_secret) {
deleted_server_message_ids.push_back(m->message_id);
}
}
}
}
bool is_bot = td_->auth_manager_->is_bot();
for (auto message_id : message_ids) {
auto m = get_message(d, message_id);
if (!can_delete_message(dialog_id, m)) {
return promise.set_error(Status::Error(400, "Message can't be deleted"));
}
if (is_bot && !message_id.is_scheduled() && message_id.is_server() && !can_revoke_message(dialog_id, m)) {
return promise.set_error(Status::Error(400, "Message can't be deleted for everyone"));
}
}
MultiPromiseActorSafe mpas{"DeleteMessagesMultiPromiseActor"};
mpas.add_promise(std::move(promise));
auto lock = mpas.get_promise();
delete_messages_on_server(dialog_id, std::move(deleted_server_message_ids), revoke, 0, mpas.get_promise());
delete_scheduled_messages_on_server(dialog_id, std::move(deleted_scheduled_server_message_ids), 0,
mpas.get_promise());
lock.set_value(Unit());
delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
}
void MessagesManager::erase_delete_messages_log_event(uint64 log_event_id) {
if (!G()->close_flag()) {
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
}
}
void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageId message_id,
MessageId old_message_id) {
// this would be a no-op, because replies have already been removed in cancel_send_message_query
// update_reply_to_message_id(dialog_id, old_message_id, message_id, false, "delete_sent_message_on_server");
// being sent message was deleted by the user or is in an inaccessible channel
// don't need to send an update to the user, because the message has already been deleted
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(INFO) << "Ignore sent " << message_id << " in inaccessible " << dialog_id;
return;
}
LOG(INFO) << "Delete already deleted sent " << message_id << " in " << dialog_id << " from server";
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (get_message_force(d, message_id, "delete_sent_message_on_server") != nullptr) {
delete_messages(dialog_id, {message_id}, true, Auto());
} else {
if (message_id.is_valid()) {
CHECK(message_id.is_server());
delete_messages_on_server(dialog_id, {message_id}, true, 0, Auto());
} else {
CHECK(message_id.is_scheduled_server());
delete_scheduled_messages_on_server(dialog_id, {message_id}, 0, Auto());
}
bool need_update_dialog_pos = false;
auto m = delete_message(d, message_id, true, &need_update_dialog_pos, "delete_sent_message_on_server");
CHECK(m == nullptr);
if (need_update_dialog_pos) { // last_clear_history_message_id might be removed
update_dialog_pos(d, "delete_sent_message_on_server");
}
}
}
class MessagesManager::DeleteMessagesOnServerLogEvent {
public:
DialogId dialog_id_;
vector<MessageId> message_ids_;
bool revoke_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(revoke_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
td::store(message_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(revoke_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
td::parse(message_ids_, parser);
}
};
uint64 MessagesManager::save_delete_messages_on_server_log_event(DialogId dialog_id,
const vector<MessageId> &message_ids, bool revoke) {
DeleteMessagesOnServerLogEvent log_event{dialog_id, message_ids, revoke};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessagesOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke,
uint64 log_event_id, Promise<Unit> &&promise) {
if (message_ids.empty()) {
return promise.set_value(Unit());
}
LOG(INFO) << (revoke ? "Revoke " : "Delete ") << format::as_array(message_ids) << " in " << dialog_id
<< " from server";
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_delete_messages_on_server_log_event(dialog_id, message_ids, revoke);
}
MultiPromiseActorSafe mpas{"DeleteMessagesOnServerMultiPromiseActor"};
mpas.add_promise(std::move(promise));
if (log_event_id != 0) {
mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), log_event_id](Unit) {
send_closure(actor_id, &MessagesManager::erase_delete_messages_log_event, log_event_id);
}));
}
auto lock = mpas.get_promise();
auto dialog_type = dialog_id.get_type();
switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel: {
auto server_message_ids = MessageId::get_server_message_ids(message_ids);
const size_t MAX_SLICE_SIZE = 100; // server side limit
for (size_t i = 0; i < server_message_ids.size(); i += MAX_SLICE_SIZE) {
auto end_i = i + MAX_SLICE_SIZE;
auto end = end_i < server_message_ids.size() ? server_message_ids.begin() + end_i : server_message_ids.end();
if (dialog_type != DialogType::Channel) {
td_->create_handler<DeleteMessagesQuery>(mpas.get_promise())
->send(dialog_id, {server_message_ids.begin() + i, end}, revoke);
} else {
td_->create_handler<DeleteChannelMessagesQuery>(mpas.get_promise())
->send(dialog_id.get_channel_id(), {server_message_ids.begin() + i, end});
}
}
break;
}
case DialogType::SecretChat: {
vector<int64> random_ids;
auto d = get_dialog_force(dialog_id, "delete_messages_on_server");
CHECK(d != nullptr);
for (auto &message_id : message_ids) {
auto *m = get_message(d, message_id);
if (m != nullptr) {
random_ids.push_back(m->random_id);
}
}
if (!random_ids.empty()) {
send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_messages, dialog_id.get_secret_chat_id(),
std::move(random_ids), mpas.get_promise());
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
lock.set_value(Unit());
}
class MessagesManager::DeleteScheduledMessagesOnServerLogEvent {
public:
DialogId dialog_id_;
vector<MessageId> message_ids_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(message_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(message_ids_, parser);
}
};
uint64 MessagesManager::save_delete_scheduled_messages_on_server_log_event(DialogId dialog_id,
const vector<MessageId> &message_ids) {
DeleteScheduledMessagesOnServerLogEvent log_event{dialog_id, message_ids};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteScheduledMessagesOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_scheduled_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids,
uint64 log_event_id, Promise<Unit> &&promise) {
if (message_ids.empty()) {
return promise.set_value(Unit());
}
LOG(INFO) << "Delete " << format::as_array(message_ids) << " in " << dialog_id << " from server";
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_delete_scheduled_messages_on_server_log_event(dialog_id, message_ids);
}
auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
promise = std::move(new_promise); // to prevent self-move
td_->create_handler<DeleteScheduledMessagesQuery>(std::move(promise))->send(dialog_id, std::move(message_ids));
}
void MessagesManager::on_failed_message_deletion(DialogId dialog_id, const vector<int32> &server_message_ids) {
if (G()->close_flag()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
vector<FullMessageId> full_message_ids;
for (auto &server_message_id : server_message_ids) {
auto message_id = MessageId(ServerMessageId(server_message_id));
d->deleted_message_ids.erase(message_id);
full_message_ids.emplace_back(dialog_id, message_id);
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
get_messages_from_server(std::move(full_message_ids), Promise<Unit>(), "on_failed_message_deletion");
}
void MessagesManager::on_failed_scheduled_message_deletion(DialogId dialog_id, const vector<MessageId> &message_ids) {
if (G()->close_flag()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto &message_id : message_ids) {
d->deleted_scheduled_server_message_ids.erase(message_id.get_scheduled_server_message_id());
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
load_dialog_scheduled_messages(dialog_id, false, 0, Promise<Unit>());
}
MessagesManager::CanDeleteDialog MessagesManager::can_delete_dialog(const Dialog *d) const {
if (is_dialog_sponsored(d)) {
auto chat_source = sponsored_dialog_source_.get_chat_source_object();
if (chat_source != nullptr) {
switch (chat_source->get_id()) {
case td_api::chatSourcePublicServiceAnnouncement::ID:
// can delete for self (but only while removing from dialog list)
return {true, false};
default:
return {false, false};
}
}
}
if (td_->auth_manager_->is_bot() || !have_input_peer(d->dialog_id, AccessRights::Read)) {
return {false, false};
}
switch (d->dialog_id.get_type()) {
case DialogType::User:
if (d->dialog_id == get_my_dialog_id() || td_->contacts_manager_->is_user_deleted(d->dialog_id.get_user_id()) ||
td_->contacts_manager_->is_user_bot(d->dialog_id.get_user_id())) {
return {true, false};
}
return {true, td_->option_manager_->get_option_boolean("revoke_pm_inbox", true)};
case DialogType::Chat:
// chats can be deleted only for self and can be deleted for everyone by their creator
return {true, td_->contacts_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()};
case DialogType::Channel:
// private supergroups can be deleted for self
return {!is_broadcast_channel(d->dialog_id) &&
!td_->contacts_manager_->is_channel_public(d->dialog_id.get_channel_id()),
td_->contacts_manager_->get_channel_can_be_deleted(d->dialog_id.get_channel_id())};
case DialogType::SecretChat:
if (td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) {
// in a closed secret chats there is no way to delete messages for both users
return {true, false};
}
// active secret chats can be deleted only for both users
return {false, true};
case DialogType::None:
default:
UNREACHABLE();
return {false, false};
}
}
void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, bool revoke,
Promise<Unit> &&promise) {
LOG(INFO) << "Receive deleteChatHistory request to delete all messages in " << dialog_id
<< ", remove_from_chat_list is " << remove_from_dialog_list << ", revoke is " << revoke;
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (is_dialog_sponsored(d)) {
auto chat_source = sponsored_dialog_source_.get_chat_source_object();
if (chat_source == nullptr || chat_source->get_id() != td_api::chatSourcePublicServiceAnnouncement::ID) {
return promise.set_error(Status::Error(400, "Can't delete the chat"));
}
if (!remove_from_dialog_list) {
return promise.set_error(Status::Error(400, "Can't delete chat history without removing the chat"));
}
removed_sponsored_dialog_id_ = dialog_id;
remove_sponsored_dialog();
td_->create_handler<HidePromoDataQuery>()->send(dialog_id);
promise.set_value(Unit());
return;
}
auto can_delete = can_delete_dialog(d);
if (revoke) {
if (!can_delete.for_all_users_) {
if (!can_delete.for_self_) {
return promise.set_error(Status::Error(400, "Chat history can't be deleted"));
}
LOG(INFO) << "Can't delete history of " << dialog_id << " for everyone; delete it only for self";
revoke = false;
}
} else {
if (!can_delete.for_self_) {
return promise.set_error(
Status::Error(400, PSLICE() << "Can't delete history of " << dialog_id << " only for self"));
}
}
auto last_new_message_id = d->last_new_message_id;
if (dialog_id.get_type() != DialogType::SecretChat && last_new_message_id == MessageId()) {
// TODO get dialog from the server and delete history from last message identifier
}
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 &&
!(old_order != DEFAULT_ORDER && remove_from_dialog_list)) {
// history has already been cleared, nothing to do
promise.set_value(Unit());
return;
}
set_dialog_max_unavailable_message_id(dialog_id, last_new_message_id, false, "delete_dialog_history");
delete_dialog_history_on_server(dialog_id, last_new_message_id, remove_from_dialog_list, revoke, allow_error, 0,
std::move(promise));
}
class MessagesManager::DeleteDialogHistoryOnServerLogEvent {
public:
DialogId dialog_id_;
MessageId max_message_id_;
bool remove_from_dialog_list_;
bool revoke_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(remove_from_dialog_list_);
STORE_FLAG(revoke_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
td::store(max_message_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(remove_from_dialog_list_);
PARSE_FLAG(revoke_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
td::parse(max_message_id_, parser);
}
};
uint64 MessagesManager::save_delete_dialog_history_on_server_log_event(DialogId dialog_id, MessageId max_message_id,
bool remove_from_dialog_list, bool revoke) {
DeleteDialogHistoryOnServerLogEvent log_event{dialog_id, max_message_id, remove_from_dialog_list, revoke};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogHistoryOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_dialog_history_on_server(DialogId dialog_id, MessageId max_message_id,
bool remove_from_dialog_list, bool revoke, bool allow_error,
uint64 log_event_id, Promise<Unit> &&promise) {
LOG(INFO) << "Delete history in " << dialog_id << " up to " << max_message_id << " from server";
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id =
save_delete_dialog_history_on_server_log_event(dialog_id, max_message_id, remove_from_dialog_list, revoke);
}
auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
promise = std::move(new_promise); // to prevent self-move
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat: {
AffectedHistoryQuery query = [td = td_, max_message_id, remove_from_dialog_list, revoke](
DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
td->create_handler<DeleteHistoryQuery>(std::move(query_promise))
->send(dialog_id, max_message_id, remove_from_dialog_list, revoke);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), false, std::move(promise));
break;
}
case DialogType::Channel:
td_->create_handler<DeleteChannelHistoryQuery>(std::move(promise))
->send(dialog_id.get_channel_id(), max_message_id, allow_error, revoke);
break;
case DialogType::SecretChat:
send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_all_messages,
dialog_id.get_secret_chat_id(), std::move(promise));
break;
case DialogType::None:
default:
UNREACHABLE();
break;
}
}
void MessagesManager::delete_topic_history(DialogId dialog_id, MessageId top_thread_message_id,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Chat info not found"));
}
// auto old_order = d->order;
// delete_all_dialog_topic_messages(d, top_thread_message_id);
delete_topic_history_on_server(dialog_id, top_thread_message_id, 0, std::move(promise));
}
class MessagesManager::DeleteTopicHistoryOnServerLogEvent {
public:
DialogId dialog_id_;
MessageId top_thread_message_id_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
END_STORE_FLAGS();
td::store(dialog_id_, storer);
td::store(top_thread_message_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
td::parse(top_thread_message_id_, parser);
}
};
uint64 MessagesManager::save_delete_topic_history_on_server_log_event(DialogId dialog_id,
MessageId top_thread_message_id) {
DeleteTopicHistoryOnServerLogEvent log_event{dialog_id, top_thread_message_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteTopicHistoryOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_topic_history_on_server(DialogId dialog_id, MessageId top_thread_message_id,
uint64 log_event_id, Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_delete_topic_history_on_server_log_event(dialog_id, top_thread_message_id);
}
auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
promise = std::move(new_promise); // to prevent self-move
AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<DeleteTopicHistoryQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
}
void MessagesManager::delete_all_call_messages(bool revoke, Promise<Unit> &&promise) {
delete_all_call_messages_on_server(revoke, 0, std::move(promise));
}
class MessagesManager::DeleteAllCallMessagesOnServerLogEvent {
public:
bool revoke_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(revoke_);
END_STORE_FLAGS();
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(revoke_);
END_PARSE_FLAGS();
}
};
uint64 MessagesManager::save_delete_all_call_messages_on_server_log_event(bool revoke) {
DeleteAllCallMessagesOnServerLogEvent log_event{revoke};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllCallMessagesOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_all_call_messages_on_server(bool revoke, uint64 log_event_id, Promise<Unit> &&promise) {
if (log_event_id == 0) {
log_event_id = save_delete_all_call_messages_on_server_log_event(revoke);
}
AffectedHistoryQuery query = [td = td_, revoke](DialogId /*dialog_id*/, Promise<AffectedHistory> &&query_promise) {
td->create_handler<DeletePhoneCallHistoryQuery>(std::move(query_promise))->send(revoke);
};
run_affected_history_query_until_complete(DialogId(), std::move(query), false,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
void MessagesManager::find_messages(const Message *m, vector<MessageId> &message_ids,
const std::function<bool(const Message *)> &condition) {
if (m == nullptr) {
return;
}
find_messages(m->left.get(), message_ids, condition);
if (condition(m)) {
message_ids.push_back(m->message_id);
}
find_messages(m->right.get(), message_ids, condition);
}
void MessagesManager::find_old_messages(const Message *m, MessageId max_message_id, vector<MessageId> &message_ids) {
if (m == nullptr) {
return;
}
find_old_messages(m->left.get(), max_message_id, message_ids);
if (m->message_id <= max_message_id) {
message_ids.push_back(m->message_id);
find_old_messages(m->right.get(), max_message_id, message_ids);
}
}
void MessagesManager::find_newer_messages(const Message *m, MessageId min_message_id, vector<MessageId> &message_ids) {
if (m == nullptr) {
return;
}
if (m->message_id > min_message_id) {
find_newer_messages(m->left.get(), min_message_id, message_ids);
message_ids.push_back(m->message_id);
}
find_newer_messages(m->right.get(), min_message_id, message_ids);
}
void MessagesManager::find_unloadable_messages(const Dialog *d, int32 unload_before_date, const Message *m,
vector<MessageId> &message_ids,
bool &has_left_to_unload_messages) const {
if (m == nullptr) {
return;
}
if (message_ids.size() >= MAX_UNLOADED_MESSAGES) {
has_left_to_unload_messages = true;
return;
}
find_unloadable_messages(d, unload_before_date, m->left.get(), message_ids, has_left_to_unload_messages);
if (can_unload_message(d, m)) {
if (m->last_access_date <= unload_before_date) {
message_ids.push_back(m->message_id);
} else {
has_left_to_unload_messages = true;
}
}
if (has_left_to_unload_messages && m->date > unload_before_date) {
// we aren't interested in unloading too new messages
return;
}
find_unloadable_messages(d, unload_before_date, m->right.get(), message_ids, has_left_to_unload_messages);
}
void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
CHECK(!is_bot);
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_sender");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return promise.set_error(Status::Error(400, "Not enough rights"));
}
if (!have_input_peer(sender_dialog_id, AccessRights::Know)) {
return promise.set_error(Status::Error(400, "Message sender not found"));
}
ChannelId channel_id;
DialogParticipantStatus channel_status = DialogParticipantStatus::Left();
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::SecretChat:
return promise.set_error(
Status::Error(400, "All messages from a sender can be deleted only in supergroup chats"));
case DialogType::Channel: {
channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->is_megagroup_channel(channel_id)) {
return promise.set_error(Status::Error(400, "The method is available only in supergroup chats"));
}
channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (!channel_status.can_delete_messages()) {
return promise.set_error(Status::Error(400, "Need delete messages administator right in the supergroup chat"));
}
channel_id = dialog_id.get_channel_id();
break;
}
case DialogType::None:
default:
UNREACHABLE();
break;
}
CHECK(channel_id.is_valid());
if (sender_dialog_id.get_type() == DialogType::SecretChat) {
return promise.set_value(Unit());
}
if (G()->parameters().use_message_db) {
LOG(INFO) << "Delete all messages from " << sender_dialog_id << " in " << dialog_id << " from database";
G()->td_db()->get_message_db_async()->delete_dialog_messages_by_sender(dialog_id, sender_dialog_id,
Auto()); // TODO Promise
}
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids, [sender_dialog_id, channel_status, is_bot](const Message *m) {
return sender_dialog_id == get_message_sender(m) && can_delete_channel_message(channel_status, m, is_bot);
});
delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, 0, std::move(promise));
}
class MessagesManager::DeleteAllChannelMessagesFromSenderOnServerLogEvent {
public:
ChannelId channel_id_;
DialogId sender_dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(channel_id_, storer);
td::store(sender_dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(channel_id_, parser);
if (parser.version() >= static_cast<int32>(Version::AddKeyboardButtonFlags)) {
td::parse(sender_dialog_id_, parser);
} else {
UserId user_id;
td::parse(user_id, parser);
sender_dialog_id_ = DialogId(user_id);
}
}
};
uint64 MessagesManager::save_delete_all_channel_messages_by_sender_on_server_log_event(ChannelId channel_id,
DialogId sender_dialog_id) {
DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event{channel_id, sender_dialog_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_all_channel_messages_by_sender_on_server(ChannelId channel_id, DialogId sender_dialog_id,
uint64 log_event_id, Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_chat_info_db) {
log_event_id = save_delete_all_channel_messages_by_sender_on_server_log_event(channel_id, sender_dialog_id);
}
AffectedHistoryQuery query = [td = td_, sender_dialog_id](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<DeleteParticipantHistoryQuery>(std::move(query_promise))
->send(dialog_id.get_channel_id(), sender_dialog_id);
};
run_affected_history_query_until_complete(DialogId(channel_id), std::move(query),
sender_dialog_id.get_type() != DialogType::User,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
void MessagesManager::delete_dialog_messages_by_date(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
CHECK(!is_bot);
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_date");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (min_date > max_date) {
return promise.set_error(Status::Error(400, "Wrong date interval specified"));
}
const int32 telegram_launch_date = 1376438400;
if (max_date < telegram_launch_date) {
return promise.set_value(Unit());
}
if (min_date < telegram_launch_date) {
min_date = telegram_launch_date;
}
auto current_date = max(G()->unix_time(), 1635000000);
if (min_date >= current_date - 30) {
return promise.set_value(Unit());
}
if (max_date >= current_date - 30) {
max_date = current_date - 31;
}
CHECK(min_date <= max_date);
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
if (revoke) {
return promise.set_error(Status::Error(400, "Bulk message revocation is unsupported in basic group chats"));
}
break;
case DialogType::Channel:
return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in supergroup chats"));
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in secret chats"));
case DialogType::None:
default:
UNREACHABLE();
break;
}
// TODO delete in database by dates
vector<MessageId> message_ids;
find_messages_by_date(d->messages.get(), min_date, max_date, message_ids);
delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
delete_dialog_messages_by_date_on_server(dialog_id, min_date, max_date, revoke, 0, std::move(promise));
}
class MessagesManager::DeleteDialogMessagesByDateOnServerLogEvent {
public:
DialogId dialog_id_;
int32 min_date_;
int32 max_date_;
bool revoke_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(revoke_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
td::store(min_date_, storer);
td::store(max_date_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(revoke_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
td::parse(min_date_, parser);
td::parse(max_date_, parser);
}
};
uint64 MessagesManager::save_delete_dialog_messages_by_date_on_server_log_event(DialogId dialog_id, int32 min_date,
int32 max_date, bool revoke) {
DeleteDialogMessagesByDateOnServerLogEvent log_event{dialog_id, min_date, max_date, revoke};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::delete_dialog_messages_by_date_on_server(DialogId dialog_id, int32 min_date, int32 max_date,
bool revoke, uint64 log_event_id,
Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_chat_info_db) {
log_event_id = save_delete_dialog_messages_by_date_on_server_log_event(dialog_id, min_date, max_date, revoke);
}
AffectedHistoryQuery query = [td = td_, min_date, max_date, revoke](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<DeleteMessagesByDateQuery>(std::move(query_promise))
->send(dialog_id, min_date, max_date, revoke);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
int32 MessagesManager::get_unload_dialog_delay() const {
constexpr int32 DIALOG_UNLOAD_DELAY = 60; // seconds
constexpr int32 DIALOG_UNLOAD_BOT_DELAY = 1800; // seconds
CHECK(is_message_unload_enabled());
auto default_unload_delay = td_->auth_manager_->is_bot() ? DIALOG_UNLOAD_BOT_DELAY : DIALOG_UNLOAD_DELAY;
return narrow_cast<int32>(td_->option_manager_->get_option_integer("message_unload_delay", default_unload_delay));
}
double MessagesManager::get_next_unload_dialog_delay(Dialog *d) const {
if (d->unload_dialog_delay_seed == 0) {
d->unload_dialog_delay_seed = Random::fast(1, 1000000000);
}
auto delay = get_unload_dialog_delay() / 4;
return delay + delay * 1e-9 * d->unload_dialog_delay_seed;
}
void MessagesManager::unload_dialog(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
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;
}
vector<MessageId> to_unload_message_ids;
bool has_left_to_unload_messages = false;
find_unloadable_messages(d, G()->unix_time_cached() - get_unload_dialog_delay() + 2, d->messages.get(),
to_unload_message_ids, has_left_to_unload_messages);
vector<int64> unloaded_message_ids;
vector<unique_ptr<Message>> unloaded_messages;
for (auto message_id : to_unload_message_ids) {
auto message = unload_message(d, message_id);
CHECK(message != nullptr);
if (message->is_update_sent) {
unloaded_message_ids.push_back(message->message_id.get());
}
unloaded_messages.push_back(std::move(message));
}
if (unloaded_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), unloaded_messages);
}
if (!to_unload_message_ids.empty() && !G()->parameters().use_message_db && !d->is_empty) {
d->have_full_history = false;
d->have_full_history_source = 0;
}
if (!unloaded_message_ids.empty()) {
send_closure_later(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateDeleteMessages>(dialog_id.get(), std::move(unloaded_message_ids), false, true));
}
if (has_left_to_unload_messages) {
LOG(DEBUG) << "Need to unload more messages in " << dialog_id;
pending_unload_dialog_timeout_.add_timeout_in(
d->dialog_id.get(),
to_unload_message_ids.size() >= MAX_UNLOADED_MESSAGES ? 1.0 : get_next_unload_dialog_delay(d));
} else {
d->has_unload_timeout = false;
}
}
void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanently_deleted) {
CHECK(d != nullptr);
LOG(INFO) << "Delete all messages in " << d->dialog_id
<< " with remove_from_dialog_list = " << remove_from_dialog_list
<< " and is_permanently_deleted = " << is_permanently_deleted;
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::DeleteAll, MessageId(), MessageContentType::None,
remove_from_dialog_list, false, false, "");
}
if (d->server_unread_count + d->local_unread_count > 0) {
MessageId max_message_id =
d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id;
if (max_message_id.is_valid()) {
read_history_inbox(d->dialog_id, max_message_id, -1, "delete_all_dialog_messages 1");
}
if (d->server_unread_count != 0 || d->local_unread_count != 0) {
set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages 2");
}
}
if (d->unread_mention_count > 0) {
set_dialog_unread_mention_count(d, 0);
send_update_chat_unread_mention_count(d);
}
if (d->unread_reaction_count > 0) {
set_dialog_unread_reaction_count(d, 0);
send_update_chat_unread_reaction_count(d, "delete_all_dialog_messages");
}
bool has_last_message_id = d->last_message_id != MessageId();
int32 last_message_date = 0;
MessageId last_clear_history_message_id;
if (!remove_from_dialog_list) {
if (has_last_message_id) {
auto m = get_message(d, d->last_message_id);
CHECK(m != nullptr);
last_message_date = m->date;
last_clear_history_message_id = d->last_message_id;
} else {
last_message_date = d->last_clear_history_date;
last_clear_history_message_id = d->last_clear_history_message_id;
}
}
vector<int64> deleted_message_ids;
do_delete_all_dialog_messages(d, d->messages, is_permanently_deleted, deleted_message_ids);
delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3");
if (is_permanently_deleted) {
for (auto id : deleted_message_ids) {
CHECK(id != 0);
d->deleted_message_ids.insert(MessageId{id});
}
}
if (d->reply_markup_message_id != MessageId()) {
set_dialog_reply_markup(d, MessageId());
}
set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages 4");
set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages 5");
set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id,
"delete_all_dialog_messages 6");
d->last_read_all_mentions_message_id = MessageId(); // it is not needed anymore
d->message_notification_group.max_removed_notification_id = NotificationId(); // it is not needed anymore
d->message_notification_group.max_removed_message_id = MessageId(); // it is not needed anymore
d->mention_notification_group.max_removed_notification_id = NotificationId(); // it is not needed anymore
d->mention_notification_group.max_removed_message_id = MessageId(); // it is not needed anymore
std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0);
d->notification_id_to_message_id.clear();
if (has_last_message_id) {
set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages 7");
send_update_chat_last_message(d, "delete_all_dialog_messages 8");
}
if (remove_from_dialog_list) {
set_dialog_order(d, DEFAULT_ORDER, true, false, "delete_all_dialog_messages 9");
} else {
update_dialog_pos(d, "delete_all_dialog_messages 10");
}
on_dialog_updated(d->dialog_id, "delete_all_dialog_messages 11");
send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), is_permanently_deleted);
}
void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise<Unit> &&promise) {
LOG(INFO) << "Delete " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "on_dialog_deleted");
if (d == nullptr) {
return promise.set_value(Unit());
}
delete_all_dialog_messages(d, true, false);
if (dialog_id.get_type() != DialogType::SecretChat) {
d->have_full_history = false;
d->have_full_history_source = 0;
d->is_empty = false;
d->need_restore_reply_markup = true;
on_dialog_updated(dialog_id, "on_dialog_deleted");
}
recently_found_dialogs_.remove_dialog(dialog_id);
recently_opened_dialogs_.remove_dialog(dialog_id);
if (dialog_id.get_type() == DialogType::Channel) {
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id));
}
close_dialog(d);
td_->forum_topic_manager_->delete_all_dialog_topics(dialog_id);
promise.set_value(Unit());
}
void MessagesManager::on_update_dialog_group_call_rights(DialogId dialog_id) {
auto d = get_dialog(dialog_id);
if (d == nullptr) {
// nothing to do
return;
}
if (d->active_group_call_id.is_valid()) {
td_->group_call_manager_->on_update_group_call_rights(d->active_group_call_id);
}
}
void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, MessageId top_thread_message_id,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_mentions");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Chat is not accessible"));
}
if (top_thread_message_id.is_valid()) {
LOG(INFO) << "Receive readAllChatMentions request in thread of " << top_thread_message_id << " in " << dialog_id;
AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<ReadMentionsQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
return;
} else {
LOG(INFO) << "Receive readAllChatMentions request in " << dialog_id << " with " << d->unread_mention_count
<< " unread mentions";
}
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(d->unread_mention_count == 0);
return promise.set_value(Unit());
}
if (d->last_new_message_id > d->last_read_all_mentions_message_id) {
d->last_read_all_mentions_message_id = d->last_new_message_id;
on_dialog_updated(dialog_id, "read_all_dialog_mentions");
}
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids, [](const Message *m) { return m->contains_unread_mention; });
LOG(INFO) << "Found " << message_ids.size() << " messages with unread mentions in memory";
bool is_update_sent = false;
for (auto message_id : message_ids) {
auto m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->contains_unread_mention);
CHECK(m->message_id == message_id);
CHECK(m->message_id.is_valid());
remove_message_notification_id(d, m, true, false); // must be called before contains_unread_mention is updated
m->contains_unread_mention = false;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageMentionRead>(dialog_id.get(), m->message_id.get(), 0));
is_update_sent = true;
on_message_changed(d, m, true, "read_all_dialog_mentions");
}
if (d->unread_mention_count != 0) {
set_dialog_unread_mention_count(d, 0);
if (!is_update_sent) {
send_update_chat_unread_mention_count(d);
} else {
LOG(INFO) << "Update unread mention message count in " << dialog_id << " to " << d->unread_mention_count;
on_dialog_updated(dialog_id, "read_all_dialog_mentions");
}
}
remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_mentions");
read_all_dialog_mentions_on_server(dialog_id, 0, std::move(promise));
}
class MessagesManager::ReadAllDialogMentionsOnServerLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_read_all_dialog_mentions_on_server_log_event(DialogId dialog_id) {
ReadAllDialogMentionsOnServerLogEvent log_event{dialog_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogMentionsOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 log_event_id,
Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_read_all_dialog_mentions_on_server_log_event(dialog_id);
}
AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
td->create_handler<ReadMentionsQuery>(std::move(query_promise))->send(dialog_id, MessageId());
};
run_affected_history_query_until_complete(dialog_id, std::move(query), false,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
void MessagesManager::read_all_dialog_reactions(DialogId dialog_id, MessageId top_thread_message_id,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_reactions");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Chat is not accessible"));
}
if (top_thread_message_id.is_valid()) {
LOG(INFO) << "Receive readAllChatReactions request in thread of " << top_thread_message_id << " in " << dialog_id;
AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<ReadReactionsQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
return;
} else {
LOG(INFO) << "Receive readAllChatReactions request in " << dialog_id << " with " << d->unread_reaction_count
<< " unread reactions";
}
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(d->unread_reaction_count == 0);
return promise.set_value(Unit());
}
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids,
[this, dialog_id](const Message *m) { return has_unread_message_reactions(dialog_id, m); });
LOG(INFO) << "Found " << message_ids.size() << " messages with unread reactions in memory";
bool is_update_sent = false;
for (auto message_id : message_ids) {
auto m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(has_unread_message_reactions(dialog_id, m));
CHECK(m->message_id == message_id);
CHECK(m->message_id.is_valid());
// remove_message_notification_id(d, m, true, false); // must be called before unread_reactions are cleared
m->reactions->unread_reactions_.clear();
send_update_message_unread_reactions(dialog_id, m, 0);
is_update_sent = true;
on_message_changed(d, m, true, "read_all_dialog_reactions");
}
if (d->unread_reaction_count != 0) {
set_dialog_unread_reaction_count(d, 0);
if (!is_update_sent) {
send_update_chat_unread_reaction_count(d, "read_all_dialog_reactions");
} else {
LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << d->unread_reaction_count;
on_dialog_updated(dialog_id, "read_all_dialog_reactions");
}
}
// remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_reactions");
read_all_dialog_reactions_on_server(dialog_id, 0, std::move(promise));
}
class MessagesManager::ReadAllDialogReactionsOnServerLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_read_all_dialog_reactions_on_server_log_event(DialogId dialog_id) {
ReadAllDialogReactionsOnServerLogEvent log_event{dialog_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogReactionsOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::read_all_dialog_reactions_on_server(DialogId dialog_id, uint64 log_event_id,
Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_read_all_dialog_reactions_on_server_log_event(dialog_id);
}
AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
td->create_handler<ReadReactionsQuery>(std::move(query_promise))->send(dialog_id, MessageId());
};
run_affected_history_query_until_complete(dialog_id, std::move(query), false,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
void MessagesManager::read_message_content_from_updates(MessageId message_id) {
if (!message_id.is_valid() || !message_id.is_server()) {
LOG(ERROR) << "Incoming update tries to read content of " << message_id;
return;
}
Dialog *d = get_dialog_by_message_id(message_id);
if (d != nullptr) {
Message *m = get_message(d, message_id);
CHECK(m != nullptr);
read_message_content(d, m, false, "read_message_content_from_updates");
}
}
void MessagesManager::read_channel_message_content_from_updates(Dialog *d, MessageId message_id) {
CHECK(d != nullptr);
if (!message_id.is_valid() || !message_id.is_server()) {
LOG(ERROR) << "Incoming update tries to read content of " << message_id << " in " << d->dialog_id;
return;
}
Message *m = get_message_force(d, message_id, "read_channel_message_content_from_updates");
if (m != nullptr) {
read_message_content(d, m, false, "read_channel_message_content_from_updates");
} else if (message_id > d->last_new_message_id && d->last_new_message_id.is_valid()) {
get_channel_difference(d->dialog_id, d->pts, true, "read_channel_message_content_from_updates");
}
}
bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source) {
LOG_CHECK(m != nullptr) << source;
CHECK(!m->message_id.is_scheduled());
bool is_mention_read = update_message_contains_unread_mention(d, m, false, "read_message_content");
bool is_content_read = update_opened_message_content(m->content.get());
if (ttl_on_open(d, m, Time::now(), is_local_read)) {
is_content_read = true;
}
LOG(INFO) << "Read message content of " << m->message_id << " in " << d->dialog_id
<< ": is_mention_read = " << is_mention_read << ", is_content_read = " << is_content_read;
if (is_mention_read || is_content_read) {
on_message_changed(d, m, true, "read_message_content");
if (is_content_read) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageContentOpened>(d->dialog_id.get(), m->message_id.get()));
}
return true;
}
return false;
}
bool MessagesManager::has_incoming_notification(DialogId dialog_id, const Message *m) const {
if (m->is_from_scheduled) {
return true;
}
return !m->is_outgoing && dialog_id != get_my_dialog_id();
}
int32 MessagesManager::calc_new_unread_count_from_last_unread(Dialog *d, MessageId max_message_id,
MessageType type) const {
CHECK(!max_message_id.is_scheduled());
MessagesConstIterator it(d, max_message_id);
if (*it == nullptr || (*it)->message_id != max_message_id) {
return -1;
}
int32 unread_count = type == MessageType::Server ? d->server_unread_count : d->local_unread_count;
while (*it != nullptr && (*it)->message_id > d->last_read_inbox_message_id) {
if (has_incoming_notification(d->dialog_id, *it) && (*it)->message_id.get_type() == type) {
unread_count--;
}
--it;
}
if (*it == nullptr || (*it)->message_id != d->last_read_inbox_message_id) {
return -1;
}
LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from last unread message";
return unread_count;
}
int32 MessagesManager::calc_new_unread_count_from_the_end(Dialog *d, MessageId max_message_id, MessageType type,
int32 hint_unread_count) const {
CHECK(!max_message_id.is_scheduled());
int32 unread_count = 0;
MessagesConstIterator it(d, MessageId::max());
while (*it != nullptr && (*it)->message_id > max_message_id) {
if (has_incoming_notification(d->dialog_id, *it) && (*it)->message_id.get_type() == type) {
unread_count++;
}
--it;
}
bool is_count_exact = d->last_message_id.is_valid() && *it != nullptr;
if (hint_unread_count >= 0) {
if (is_count_exact) {
if (hint_unread_count == unread_count) {
return hint_unread_count;
}
} else {
if (hint_unread_count >= unread_count) {
return hint_unread_count;
}
}
// hint_unread_count is definitely wrong, ignore it
if (need_unread_counter(d->order)) {
LOG(ERROR) << "Receive hint_unread_count = " << hint_unread_count << ", but found " << unread_count
<< " unread messages in " << d->dialog_id;
}
}
if (!is_count_exact) {
// unread count is likely to be calculated wrong, so ignore it
return -1;
}
LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from the end";
return unread_count;
}
int32 MessagesManager::calc_new_unread_count(Dialog *d, MessageId max_message_id, MessageType type,
int32 hint_unread_count) const {
CHECK(!max_message_id.is_scheduled());
if (d->is_empty) {
return 0;
}
if (!d->last_read_inbox_message_id.is_valid()) {
return calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
}
if (!d->last_message_id.is_valid() ||
(d->last_message_id.get() - max_message_id.get() > max_message_id.get() - d->last_read_inbox_message_id.get())) {
int32 unread_count = calc_new_unread_count_from_last_unread(d, max_message_id, type);
return unread_count >= 0 ? unread_count
: calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
} else {
int32 unread_count = calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
return unread_count >= 0 ? unread_count : calc_new_unread_count_from_last_unread(d, max_message_id, type);
}
}
void MessagesManager::repair_server_unread_count(DialogId dialog_id, int32 unread_count, const char *source) {
if (td_->auth_manager_->is_bot() || !have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
if (pending_read_history_timeout_.has_timeout(dialog_id.get())) {
return; // postpone until read history request is sent
}
LOG(INFO) << "Repair server unread count in " << dialog_id << " from " << unread_count << " from " << source;
create_actor<SleepActor>("RepairServerUnreadCountSleepActor", 0.2,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::send_get_dialog_query, dialog_id, Promise<Unit>(),
0, "repair_server_unread_count");
}))
.release();
}
void MessagesManager::repair_channel_server_unread_count(Dialog *d) {
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() == DialogType::Channel);
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->last_read_inbox_message_id >= d->last_new_message_id) {
// all messages are already read
return;
}
if (!need_unread_counter(d->order)) {
// there are no unread counters in left channels
return;
}
if (!d->need_repair_channel_server_unread_count) {
d->need_repair_channel_server_unread_count = true;
on_dialog_updated(d->dialog_id, "repair_channel_server_unread_count");
}
LOG(INFO) << "Reload ChannelFull for " << d->dialog_id << " to repair unread message counts";
get_dialog_info_full(d->dialog_id, Auto(), "repair_channel_server_unread_count");
}
void MessagesManager::read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count,
const char *source) {
CHECK(!max_message_id.is_scheduled());
if (td_->auth_manager_->is_bot()) {
return;
}
Dialog *d = get_dialog_force(dialog_id, "read_history_inbox");
if (d != nullptr) {
if (d->need_repair_channel_server_unread_count) {
d->need_repair_channel_server_unread_count = false;
on_dialog_updated(dialog_id, "read_history_inbox");
}
// there can be updateReadHistoryInbox up to message 0, if messages where read and then all messages where deleted
if (!max_message_id.is_valid() && max_message_id != MessageId()) {
LOG(ERROR) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source;
return;
}
if (d->is_last_read_inbox_message_id_inited && max_message_id <= d->last_read_inbox_message_id) {
LOG(INFO) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source
<< ", but all messages have already been read up to " << d->last_read_inbox_message_id;
if (max_message_id == d->last_read_inbox_message_id && unread_count >= 0 &&
unread_count != d->server_unread_count) {
set_dialog_last_read_inbox_message_id(d, MessageId::min(), unread_count, d->local_unread_count, true, source);
}
return;
}
if (max_message_id != MessageId() && max_message_id.is_yet_unsent()) {
LOG(ERROR) << "Tried to update last read inbox message in " << dialog_id << " with " << max_message_id << " from "
<< source;
return;
}
if (max_message_id != MessageId() && unread_count > 0 && max_message_id >= d->last_new_message_id &&
max_message_id >= d->last_message_id && max_message_id >= d->last_database_message_id) {
if (d->last_new_message_id.is_valid()) {
LOG(ERROR) << "Have unknown " << unread_count << " unread messages up to " << max_message_id << " in "
<< dialog_id << " with last_new_message_id = " << d->last_new_message_id
<< ", last_message_id = " << d->last_message_id
<< ", last_database_message_id = " << d->last_database_message_id << " from " << source;
}
unread_count = 0;
}
LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id &&
max_message_id > d->max_notification_message_id && max_message_id.is_server() &&
dialog_id.get_type() != DialogType::Channel && !running_get_difference_)
<< "Receive read inbox update up to unknown " << max_message_id << " in " << dialog_id << " from " << source
<< ". Last new is " << d->last_new_message_id << ", unread_count = " << unread_count
<< ". Possible only for deleted incoming message";
if (dialog_id.get_type() == DialogType::SecretChat) {
ttl_read_history(d, false, max_message_id, d->last_read_inbox_message_id, Time::now());
}
if (max_message_id > d->last_new_message_id && dialog_id.get_type() == DialogType::Channel) {
LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
int32 server_unread_count = calc_new_unread_count(d, max_message_id, MessageType::Server, unread_count);
int32 local_unread_count =
d->local_unread_count == 0 ? 0 : calc_new_unread_count(d, max_message_id, MessageType::Local, -1);
if (server_unread_count < 0) {
server_unread_count = unread_count >= 0 ? unread_count : d->server_unread_count;
if (dialog_id.get_type() != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read) &&
need_unread_counter(d->order)) {
d->need_repair_server_unread_count = true;
repair_server_unread_count(dialog_id, server_unread_count, "read_history_inbox");
}
}
if (local_unread_count < 0) {
// TODO repair local unread count
local_unread_count = d->local_unread_count;
}
set_dialog_last_read_inbox_message_id(d, max_message_id, server_unread_count, local_unread_count, true, source);
if (d->is_marked_as_unread && max_message_id != MessageId()) {
set_dialog_is_marked_as_unread(d, false);
}
} else {
LOG(INFO) << "Receive read inbox about unknown " << dialog_id << " from " << source;
}
}
void MessagesManager::read_history_outbox(DialogId dialog_id, MessageId max_message_id, int32 read_date) {
CHECK(!max_message_id.is_scheduled());
if (td_->auth_manager_->is_bot()) {
return;
}
Dialog *d = get_dialog_force(dialog_id, "read_history_outbox");
if (d != nullptr) {
if (!max_message_id.is_valid()) {
LOG(ERROR) << "Receive read outbox update in " << dialog_id << " with " << max_message_id;
return;
}
if (max_message_id <= d->last_read_outbox_message_id) {
LOG(INFO) << "Receive read outbox update up to " << max_message_id
<< ", but all messages have already been read up to " << d->last_read_outbox_message_id;
return;
}
if (max_message_id.is_yet_unsent()) {
LOG(ERROR) << "Tried to update last read outbox message with " << max_message_id << " in " << dialog_id;
return;
}
// it is impossible for just sent outgoing messages because updates are ordered by pts
if (d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id &&
dialog_id.get_type() != DialogType::Channel) {
LOG(INFO) << "Receive read outbox update about unknown " << max_message_id << " in " << dialog_id
<< " with last new " << d->last_new_message_id << ". Possible only for deleted outgoing message";
}
if (dialog_id.get_type() == DialogType::SecretChat) {
double server_time = G()->server_time();
double read_time = Time::now();
if (read_date <= 0) {
LOG(ERROR) << "Receive wrong read date " << read_date << " in " << dialog_id;
} else if (read_date < server_time) {
read_time -= (server_time - read_date);
}
ttl_read_history(d, true, max_message_id, d->last_read_outbox_message_id, read_time);
}
set_dialog_last_read_outbox_message_id(d, max_message_id);
} else {
LOG(INFO) << "Receive read outbox update about unknown " << dialog_id;
}
}
bool MessagesManager::need_unread_counter(int64 dialog_order) {
return dialog_order != DEFAULT_ORDER;
}
int32 MessagesManager::get_dialog_total_count(const DialogList &list) const {
int32 sponsored_dialog_count = 0;
if (sponsored_dialog_id_.is_valid() && list.dialog_list_id == DialogListId(FolderId::main())) {
auto d = get_dialog(sponsored_dialog_id_);
CHECK(d != nullptr);
if (is_dialog_sponsored(d)) {
sponsored_dialog_count = 1;
}
}
if (list.server_dialog_total_count_ != -1 && list.secret_chat_total_count_ != -1) {
return std::max(list.server_dialog_total_count_ + list.secret_chat_total_count_,
list.in_memory_dialog_total_count_) +
sponsored_dialog_count;
}
if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
return list.in_memory_dialog_total_count_ + sponsored_dialog_count;
}
return list.in_memory_dialog_total_count_ + sponsored_dialog_count + 1;
}
void MessagesManager::repair_server_dialog_total_count(DialogListId dialog_list_id) {
if (G()->close_flag()) {
return;
}
if (td_->auth_manager_->is_bot()) {
return;
}
if (!dialog_list_id.is_folder()) {
// can repair total count only in folders
return;
}
LOG(INFO) << "Repair total chat count in " << dialog_list_id;
td_->create_handler<GetDialogListQuery>(Promise<Unit>())
->send(dialog_list_id.get_folder_id(), 2147483647, ServerMessageId(), DialogId(), 1);
}
void MessagesManager::repair_secret_chat_total_count(DialogListId dialog_list_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (G()->parameters().use_message_db && dialog_list_id.is_folder()) {
// race-prone
G()->td_db()->get_dialog_db_async()->get_secret_chat_count(
dialog_list_id.get_folder_id(),
PromiseCreator::lambda([actor_id = actor_id(this), dialog_list_id](Result<int32> result) {
if (result.is_error()) {
return;
}
send_closure(actor_id, &MessagesManager::on_get_secret_chat_total_count, dialog_list_id, result.move_as_ok());
}));
} else {
int32 total_count = 0;
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
for (auto &folder_id : get_dialog_list_folder_ids(*list)) {
const auto *folder_list = get_dialog_list(DialogListId(folder_id));
CHECK(folder_list != nullptr);
if (folder_list->need_unread_count_recalc_) {
// can't repair total secret chat count yet
return;
}
const auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
for (const auto &dialog_date : folder->ordered_dialogs_) {
auto dialog_id = dialog_date.get_dialog_id();
if (dialog_id.get_type() == DialogType::SecretChat && dialog_date.get_order() != DEFAULT_ORDER) {
total_count++;
}
}
}
on_get_secret_chat_total_count(dialog_list_id, total_count);
}
}
void MessagesManager::on_get_secret_chat_total_count(DialogListId dialog_list_id, int32 total_count) {
if (G()->close_flag()) {
return;
}
CHECK(!td_->auth_manager_->is_bot());
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr) {
// just in case
return;
}
CHECK(total_count >= 0);
if (list->secret_chat_total_count_ != total_count) {
auto old_dialog_total_count = get_dialog_total_count(*list);
list->secret_chat_total_count_ = total_count;
if (list->is_dialog_unread_count_inited_) {
if (old_dialog_total_count != get_dialog_total_count(*list)) {
send_update_unread_chat_count(*list, DialogId(), true, "on_get_secret_chat_total_count");
} else {
save_unread_chat_count(*list);
}
}
}
}
void MessagesManager::recalc_unread_count(DialogListId dialog_list_id, int32 old_dialog_total_count, bool force) {
if (G()->close_flag() || td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
return;
}
auto *list_ptr = get_dialog_list(dialog_list_id);
CHECK(list_ptr != nullptr);
auto &list = *list_ptr;
if (!list.need_unread_count_recalc_ && !force) {
return;
}
LOG(INFO) << "Recalculate unread counts in " << dialog_list_id;
list.need_unread_count_recalc_ = false;
list.is_message_unread_count_inited_ = true;
list.is_dialog_unread_count_inited_ = true;
int32 message_total_count = 0;
int32 message_muted_count = 0;
int32 dialog_total_count = 0;
int32 dialog_muted_count = 0;
int32 dialog_marked_count = 0;
int32 dialog_muted_marked_count = 0;
int32 server_dialog_total_count = 0;
int32 secret_chat_total_count = 0;
for (auto folder_id : get_dialog_list_folder_ids(list)) {
const auto &folder = *get_dialog_folder(folder_id);
for (const auto &dialog_date : folder.ordered_dialogs_) {
if (dialog_date.get_order() == DEFAULT_ORDER) {
break;
}
auto dialog_id = dialog_date.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!is_dialog_in_list(d, dialog_list_id)) {
continue;
}
int unread_count = d->server_unread_count + d->local_unread_count;
if (need_unread_counter(d->order) && (unread_count > 0 || d->is_marked_as_unread)) {
message_total_count += unread_count;
dialog_total_count++;
if (unread_count == 0 && d->is_marked_as_unread) {
dialog_marked_count++;
}
LOG(DEBUG) << "Have " << unread_count << " messages in " << dialog_id;
if (is_dialog_muted(d)) {
message_muted_count += unread_count;
dialog_muted_count++;
if (unread_count == 0 && d->is_marked_as_unread) {
dialog_muted_marked_count++;
}
}
}
if (d->order != DEFAULT_ORDER) { // must not count sponsored dialog, which is added independently
if (dialog_id.get_type() == DialogType::SecretChat) {
secret_chat_total_count++;
} else {
server_dialog_total_count++;
}
}
}
}
if (list.unread_message_total_count_ != message_total_count ||
list.unread_message_muted_count_ != message_muted_count) {
list.unread_message_total_count_ = message_total_count;
list.unread_message_muted_count_ = message_muted_count;
send_update_unread_message_count(list, DialogId(), true, "recalc_unread_count");
}
if (old_dialog_total_count == -1) {
old_dialog_total_count = get_dialog_total_count(list);
}
bool need_save = false;
if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
if (server_dialog_total_count != list.server_dialog_total_count_ ||
secret_chat_total_count != list.secret_chat_total_count_) {
list.server_dialog_total_count_ = server_dialog_total_count;
list.secret_chat_total_count_ = secret_chat_total_count;
need_save = true;
}
} else {
if (list.server_dialog_total_count_ == -1) {
// recalc_unread_count is called only after getDialogs request; it is unneeded to call getDialogs again
repair_server_dialog_total_count(dialog_list_id);
}
if (list.secret_chat_total_count_ == -1) {
repair_secret_chat_total_count(dialog_list_id);
}
}
if (list.unread_dialog_total_count_ != dialog_total_count || list.unread_dialog_muted_count_ != dialog_muted_count ||
list.unread_dialog_marked_count_ != dialog_marked_count ||
list.unread_dialog_muted_marked_count_ != dialog_muted_marked_count ||
old_dialog_total_count != get_dialog_total_count(list)) {
list.unread_dialog_total_count_ = dialog_total_count;
list.unread_dialog_muted_count_ = dialog_muted_count;
list.unread_dialog_marked_count_ = dialog_marked_count;
list.unread_dialog_muted_marked_count_ = dialog_muted_marked_count;
send_update_unread_chat_count(list, DialogId(), true, "recalc_unread_count");
} else if (need_save) {
save_unread_chat_count(list);
}
}
void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId message_id, int32 server_unread_count,
int32 local_unread_count, bool force_update,
const char *source) {
CHECK(!message_id.is_scheduled());
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG(INFO) << "Update last read inbox message in " << d->dialog_id << " from " << d->last_read_inbox_message_id
<< " to " << message_id << " and update unread message count from " << d->server_unread_count << " + "
<< d->local_unread_count << " to " << server_unread_count << " + " << local_unread_count << " from "
<< source;
if (message_id != MessageId::min()) {
d->last_read_inbox_message_id = message_id;
d->is_last_read_inbox_message_id_inited = true;
}
int32 old_unread_count = d->server_unread_count + d->local_unread_count;
d->server_unread_count = server_unread_count;
d->local_unread_count = local_unread_count;
if (need_unread_counter(d->order)) {
const int32 new_unread_count = d->server_unread_count + d->local_unread_count;
for (auto &list : get_dialog_lists(d)) {
int32 delta = new_unread_count - old_unread_count;
if (delta != 0 && list.is_message_unread_count_inited_) {
list.unread_message_total_count_ += delta;
if (is_dialog_muted(d)) {
list.unread_message_muted_count_ += delta;
}
send_update_unread_message_count(list, d->dialog_id, force_update, source);
}
delta = static_cast<int32>(new_unread_count != 0) - static_cast<int32>(old_unread_count != 0);
if (delta != 0 && list.is_dialog_unread_count_inited_) {
if (d->is_marked_as_unread) {
list.unread_dialog_marked_count_ -= delta;
} else {
list.unread_dialog_total_count_ += delta;
}
if (is_dialog_muted(d)) {
if (d->is_marked_as_unread) {
list.unread_dialog_muted_marked_count_ -= delta;
} else {
list.unread_dialog_muted_count_ += delta;
}
}
send_update_unread_chat_count(list, d->dialog_id, force_update, source);
}
}
bool was_unread = old_unread_count != 0 || d->is_marked_as_unread;
bool is_unread = new_unread_count != 0 || d->is_marked_as_unread;
if (!dialog_filters_.empty() && was_unread != is_unread) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_last_read_inbox_message_id");
}
}
if (message_id != MessageId::min() && d->last_read_inbox_message_id.is_valid() &&
(d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
VLOG(notifications) << "Remove some notifications in " << d->dialog_id
<< " after updating last read inbox message to " << message_id
<< " and unread message count to " << server_unread_count << " + " << local_unread_count
<< " from " << source;
if (d->message_notification_group.group_id.is_valid()) {
auto total_count = get_dialog_pending_notification_count(d, false);
if (total_count == 0) {
set_dialog_last_notification(d->dialog_id, d->message_notification_group, 0, NotificationId(), source);
}
if (!d->pending_new_message_notifications.empty()) {
for (auto &it : d->pending_new_message_notifications) {
if (it.second <= message_id) {
it.first = DialogId();
}
}
flush_pending_new_message_notifications(d->dialog_id, false, DialogId(UserId(static_cast<int64>(1))));
}
total_count -= static_cast<int32>(d->pending_new_message_notifications.size());
if (total_count < 0) {
LOG(ERROR) << "Total message notification count is " << total_count << " in " << d->dialog_id
<< " with old unread_count = " << old_unread_count << " and " << d->pending_new_message_notifications
<< " pending new message notifications after reading history up to " << message_id;
total_count = 0;
}
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group,
d->message_notification_group.group_id, NotificationId(), d->last_read_inbox_message_id,
total_count, Slice(source) == Slice("view_messages"), Promise<Unit>());
}
if (d->mention_notification_group.group_id.is_valid() && d->pinned_message_notification_message_id.is_valid() &&
d->pinned_message_notification_message_id <= d->last_read_inbox_message_id) {
// remove pinned message notification when it is read
remove_dialog_pinned_message_notification(d, source);
}
}
send_update_chat_read_inbox(d, force_update, source);
}
void MessagesManager::set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id) {
CHECK(!message_id.is_scheduled());
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG(INFO) << "Update last read outbox message in " << d->dialog_id << " from " << d->last_read_outbox_message_id
<< " to " << message_id;
d->last_read_outbox_message_id = message_id;
d->is_last_read_outbox_message_id_inited = true;
send_update_chat_read_outbox(d);
}
void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id, MessageId max_unavailable_message_id,
bool from_update, const char *source) {
CHECK(!max_unavailable_message_id.is_scheduled());
Dialog *d = get_dialog_force(dialog_id, source);
if (d != nullptr) {
if (d->last_new_message_id.is_valid() && max_unavailable_message_id > d->last_new_message_id && from_update) {
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Tried to set " << dialog_id << " max unavailable message to " << max_unavailable_message_id
<< " from " << source << ", but last new message is " << d->last_new_message_id;
}
max_unavailable_message_id = d->last_new_message_id;
}
if (d->max_unavailable_message_id == max_unavailable_message_id) {
return;
}
if (max_unavailable_message_id.is_valid() && max_unavailable_message_id.is_yet_unsent()) {
LOG(ERROR) << "Tried to update " << dialog_id << " max unavailable message with " << max_unavailable_message_id
<< " from " << source;
return;
}
LOG(INFO) << "Set max unavailable message to " << max_unavailable_message_id << " in " << dialog_id << " from "
<< source;
on_dialog_updated(dialog_id, "set_dialog_max_unavailable_message_id");
if (d->max_unavailable_message_id > max_unavailable_message_id) {
d->max_unavailable_message_id = max_unavailable_message_id;
return;
}
d->max_unavailable_message_id = max_unavailable_message_id;
vector<MessageId> message_ids;
find_old_messages(d->messages.get(), max_unavailable_message_id, message_ids);
vector<int64> deleted_message_ids;
bool need_update_dialog_pos = false;
for (auto message_id : message_ids) {
if (message_id.is_yet_unsent()) {
continue;
}
auto m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->message_id <= max_unavailable_message_id);
CHECK(m->message_id == message_id);
auto p =
delete_message(d, message_id, !from_update, &need_update_dialog_pos, "set_dialog_max_unavailable_message_id");
CHECK(p.get() == m);
deleted_message_ids.push_back(p->message_id.get());
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "set_dialog_max_unavailable_message_id");
}
send_update_delete_messages(dialog_id, std::move(deleted_message_ids), !from_update);
if (d->server_unread_count + d->local_unread_count > 0) {
read_history_inbox(dialog_id, max_unavailable_message_id, -1, "set_dialog_max_unavailable_message_id");
}
} else {
LOG(INFO) << "Receive max unavailable message in unknown " << dialog_id << " from " << source;
}
}
void MessagesManager::set_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server,
const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
Dialog *d = get_dialog(dialog_id);
if (d == nullptr) {
return;
}
if (online_member_count < 0) {
LOG(ERROR) << "Receive online_member_count = " << online_member_count << " in " << dialog_id;
online_member_count = 0;
}
switch (dialog_id.get_type()) {
case DialogType::Chat: {
auto participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id());
if (online_member_count > participant_count) {
online_member_count = participant_count;
}
break;
}
case DialogType::Channel: {
auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
if (participant_count != 0 && online_member_count > participant_count) {
online_member_count = participant_count;
}
break;
}
default:
break;
}
auto &info = dialog_online_member_counts_[dialog_id];
LOG(INFO) << "Change number of online members from " << info.online_member_count << " to " << online_member_count
<< " in " << dialog_id << " from " << source;
bool need_update = d->is_opened && (!info.is_update_sent || info.online_member_count != online_member_count);
info.online_member_count = online_member_count;
info.update_time = Time::now();
if (need_update) {
info.is_update_sent = true;
send_update_chat_online_member_count(dialog_id, online_member_count);
}
if (d->is_opened) {
if (is_from_server) {
update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME);
} else {
update_dialog_online_member_count_timeout_.add_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME);
}
}
}
void MessagesManager::on_update_dialog_online_member_count_timeout(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Expired timeout for number of online members in " << dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!d->is_opened) {
send_update_chat_online_member_count(dialog_id, 0);
return;
}
if (dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id)) {
auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
auto has_hidden_participants =
td_->contacts_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id());
if (participant_count == 0 || participant_count >= 195 || has_hidden_participants) {
td_->create_handler<GetOnlinesQuery>()->send(dialog_id);
} else {
td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(),
td_api::make_object<td_api::supergroupMembersFilterRecent>(),
string(), 0, 200, 200, Auto());
}
return;
}
if (dialog_id.get_type() == DialogType::Chat) {
// we need actual online status state, so we need to reget chat participants
td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id());
return;
}
}
void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Expired timeout for updating of recently viewed messages in " << dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!d->is_opened) {
return;
}
auto it = dialog_viewed_messages_.find(dialog_id);
if (it == dialog_viewed_messages_.end() || !td_->is_online()) {
return;
}
auto &info = it->second;
CHECK(info != nullptr);
vector<MessageId> reaction_message_ids;
vector<MessageId> views_message_ids;
vector<MessageId> extended_media_message_ids;
for (auto &message_it : info->message_id_to_view_id) {
Message *m = get_message_force(d, message_it.first, "on_update_viewed_messages_timeout");
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
CHECK(m->message_id.is_server());
if (need_poll_message_reactions(d, m)) {
reaction_message_ids.push_back(m->message_id);
}
if (m->view_count > 0 && !m->has_get_message_views_query) {
m->has_get_message_views_query = true;
views_message_ids.push_back(m->message_id);
}
if (need_poll_message_content_extended_media(m->content.get()) && !m->has_get_extended_media_query) {
m->has_get_extended_media_query = true;
extended_media_message_ids.push_back(m->message_id);
}
}
if (!reaction_message_ids.empty()) {
queue_message_reactions_reload(dialog_id, reaction_message_ids);
}
if (!views_message_ids.empty()) {
td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(views_message_ids), false);
}
if (!extended_media_message_ids.empty()) {
td_->create_handler<GetExtendedMediaQuery>()->send(dialog_id, std::move(extended_media_message_ids));
}
update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD);
}
int32 MessagesManager::get_message_date(const tl_object_ptr<telegram_api::Message> &message_ptr) {
switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID:
return 0;
case telegram_api::message::ID: {
auto message = static_cast<const telegram_api::message *>(message_ptr.get());
return message->date_;
}
case telegram_api::messageService::ID: {
auto message = static_cast<const telegram_api::messageService *>(message_ptr.get());
return message->date_;
}
default:
UNREACHABLE();
return 0;
}
}
void MessagesManager::ttl_read_history(Dialog *d, bool is_outgoing, MessageId from_message_id,
MessageId till_message_id, double view_date) {
CHECK(!from_message_id.is_scheduled());
CHECK(!till_message_id.is_scheduled());
// TODO: protect with log event
suffix_load_till_message_id(d, till_message_id,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, is_outgoing,
from_message_id, till_message_id, view_date](Result<Unit>) {
send_closure(actor_id, &MessagesManager::ttl_read_history_impl, dialog_id, is_outgoing,
from_message_id, till_message_id, view_date);
}));
}
void MessagesManager::ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id,
MessageId till_message_id, double view_date) {
CHECK(!from_message_id.is_scheduled());
CHECK(!till_message_id.is_scheduled());
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto now = Time::now();
for (auto it = MessagesIterator(d, from_message_id); *it && (*it)->message_id >= till_message_id; --it) {
auto *m = *it;
if (m->is_outgoing == is_outgoing) {
ttl_on_view(d, m, view_date, now);
}
}
}
void MessagesManager::ttl_on_view(const Dialog *d, Message *m, double view_date, double now) {
if (m->ttl > 0 && m->ttl_expires_at == 0 && !m->message_id.is_scheduled() && !m->message_id.is_yet_unsent() &&
!m->is_failed_to_send && !m->is_content_secret) {
m->ttl_expires_at = m->ttl + view_date;
ttl_register_message(d->dialog_id, m, now);
on_message_changed(d, m, true, "ttl_on_view");
}
}
bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read) {
CHECK(!m->message_id.is_scheduled());
if (m->ttl > 0 && m->ttl_expires_at == 0) {
if (!is_local_read && d->dialog_id.get_type() != DialogType::SecretChat) {
on_message_ttl_expired(d, m);
} else {
m->ttl_expires_at = m->ttl + now;
ttl_register_message(d->dialog_id, m, now);
}
return true;
}
return false;
}
void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *m, double now) {
CHECK(m != nullptr);
CHECK(m->ttl_expires_at != 0);
CHECK(!m->message_id.is_scheduled());
auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, false);
CHECK(it_flag.second);
auto it = it_flag.first;
ttl_heap_.insert(m->ttl_expires_at, it->as_heap_node());
ttl_update_timeout(now);
}
void MessagesManager::ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time) {
CHECK(m != nullptr);
CHECK(m->ttl_period != 0);
CHECK(!m->message_id.is_scheduled());
auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, true);
CHECK(it_flag.second);
auto it = it_flag.first;
auto now = Time::now();
ttl_heap_.insert(now + (m->date + m->ttl_period - server_time), it->as_heap_node());
ttl_update_timeout(now);
}
void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source) {
if (m->ttl_expires_at == 0) {
return;
}
CHECK(!m->message_id.is_scheduled());
auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, false));
// expect m->ttl == 0, but m->ttl_expires_at > 0 from binlog
LOG_CHECK(it != ttl_nodes_.end()) << dialog_id << " " << m->message_id << " " << source << " " << G()->close_flag()
<< " " << m->ttl << " " << m->ttl_expires_at << " " << Time::now() << " "
<< m->from_database;
auto *heap_node = it->as_heap_node();
if (heap_node->in_heap()) {
ttl_heap_.erase(heap_node);
}
ttl_nodes_.erase(it);
ttl_update_timeout(Time::now());
}
void MessagesManager::ttl_period_unregister_message(DialogId dialog_id, const Message *m) {
if (m->ttl_period == 0) {
return;
}
CHECK(!m->message_id.is_scheduled());
auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, true));
CHECK(it != ttl_nodes_.end());
auto *heap_node = it->as_heap_node();
if (heap_node->in_heap()) {
ttl_heap_.erase(heap_node);
}
ttl_nodes_.erase(it);
ttl_update_timeout(Time::now());
}
void MessagesManager::ttl_loop(double now) {
FlatHashMap<DialogId, std::vector<MessageId>, DialogIdHash> to_delete;
while (!ttl_heap_.empty() && ttl_heap_.top_key() < now) {
TtlNode *ttl_node = TtlNode::from_heap_node(ttl_heap_.pop());
auto full_message_id = ttl_node->full_message_id_;
auto dialog_id = full_message_id.get_dialog_id();
if (dialog_id.get_type() == DialogType::SecretChat || ttl_node->by_ttl_period_) {
to_delete[dialog_id].push_back(full_message_id.get_message_id());
} else {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
on_message_ttl_expired(d, m);
on_message_changed(d, m, true, "ttl_loop");
}
}
for (auto &it : to_delete) {
delete_dialog_messages(it.first, it.second, false, "ttl_loop");
}
ttl_update_timeout(now);
}
void MessagesManager::ttl_update_timeout(double now) {
if (ttl_heap_.empty()) {
if (!ttl_slot_.empty()) {
ttl_slot_.cancel_timeout();
}
return;
}
ttl_slot_.set_event(EventCreator::yield(actor_id()));
ttl_slot_.set_timeout_in(ttl_heap_.top_key() - now);
}
void MessagesManager::on_message_ttl_expired(Dialog *d, Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->ttl > 0);
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
ttl_unregister_message(d->dialog_id, m, "on_message_ttl_expired");
unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired");
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, m, true, "on_message_ttl_expired");
// the caller must call on_message_changed
}
void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
CHECK(!m->message_id.is_yet_unsent());
CHECK(m->ttl > 0);
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
delete_message_files(d->dialog_id, m);
update_expired_message_content(m->content);
m->ttl = 0;
m->ttl_expires_at = 0;
if (m->reply_markup != nullptr) {
if (m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
if (!td_->auth_manager_->is_bot()) {
if (d->reply_markup_message_id == m->message_id) {
set_dialog_reply_markup(d, MessageId());
}
}
m->had_reply_markup = true;
}
m->reply_markup = nullptr;
}
remove_message_notification_id(d, m, true, true);
update_message_contains_unread_mention(d, m, false, "on_message_ttl_expired_impl");
remove_message_unread_reactions(d, m, "on_message_ttl_expired_impl");
unregister_message_reply(d->dialog_id, m);
m->noforwards = false;
m->contains_mention = false;
m->reply_to_message_id = MessageId();
m->reply_to_random_id = 0;
m->max_reply_media_timestamp = -1;
m->reply_in_dialog_id = DialogId();
m->linked_top_thread_message_id = MessageId();
m->is_content_secret = false;
}
void MessagesManager::loop() {
auto token = get_link_token();
if (token == YieldType::TtlDb) {
ttl_db_loop(G()->server_time());
} else {
ttl_loop(Time::now());
}
}
class MessagesManager::DialogFiltersLogEvent {
public:
int32 server_main_dialog_list_position = 0;
int32 main_dialog_list_position = 0;
int32 updated_date = 0;
const vector<unique_ptr<DialogFilter>> *server_dialog_filters_in;
const vector<unique_ptr<DialogFilter>> *dialog_filters_in;
vector<unique_ptr<DialogFilter>> server_dialog_filters_out;
vector<unique_ptr<DialogFilter>> dialog_filters_out;
template <class StorerT>
void store(StorerT &storer) const {
bool has_server_dialog_filters = !server_dialog_filters_in->empty();
bool has_dialog_filters = !dialog_filters_in->empty();
bool has_server_main_dialog_list_position = server_main_dialog_list_position != 0;
bool has_main_dialog_list_position = main_dialog_list_position != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_server_dialog_filters);
STORE_FLAG(has_dialog_filters);
STORE_FLAG(has_server_main_dialog_list_position);
STORE_FLAG(has_main_dialog_list_position);
END_STORE_FLAGS();
td::store(updated_date, storer);
if (has_server_dialog_filters) {
td::store(*server_dialog_filters_in, storer);
}
if (has_dialog_filters) {
td::store(*dialog_filters_in, storer);
}
if (has_server_main_dialog_list_position) {
td::store(server_main_dialog_list_position, storer);
}
if (has_main_dialog_list_position) {
td::store(main_dialog_list_position, storer);
}
}
template <class ParserT>
void parse(ParserT &parser) {
bool has_server_dialog_filters = true;
bool has_dialog_filters = true;
bool has_server_main_dialog_list_position = false;
bool has_main_dialog_list_position = false;
if (parser.version() >= static_cast<int32>(Version::AddMainDialogListPosition)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_server_dialog_filters);
PARSE_FLAG(has_dialog_filters);
PARSE_FLAG(has_server_main_dialog_list_position);
PARSE_FLAG(has_main_dialog_list_position);
END_PARSE_FLAGS();
}
td::parse(updated_date, parser);
if (has_server_dialog_filters) {
td::parse(server_dialog_filters_out, parser);
}
if (has_dialog_filters) {
td::parse(dialog_filters_out, parser);
}
if (has_server_main_dialog_list_position) {
td::parse(server_main_dialog_list_position, parser);
}
if (has_main_dialog_list_position) {
td::parse(main_dialog_list_position, parser);
}
}
};
void MessagesManager::tear_down() {
parent_.reset();
LOG(DEBUG) << "Have " << dialogs_.calc_size() << " chats with " << added_message_count_ << " messages to free";
}
void MessagesManager::hangup() {
postponed_channel_updates_.clear();
if (!G()->parameters().use_message_db) {
while (!being_uploaded_files_.empty()) {
auto it = being_uploaded_files_.begin();
auto full_message_id = it->second.first;
being_uploaded_files_.erase(it);
if (full_message_id.get_message_id().is_yet_unsent()) {
fail_send_message(full_message_id, Global::request_aborted_error());
}
}
while (!being_uploaded_thumbnails_.empty()) {
auto it = being_uploaded_thumbnails_.begin();
auto full_message_id = it->second.full_message_id;
being_uploaded_thumbnails_.erase(it);
if (full_message_id.get_message_id().is_yet_unsent()) {
fail_send_message(full_message_id, Global::request_aborted_error());
}
}
while (!being_loaded_secret_thumbnails_.empty()) {
auto it = being_loaded_secret_thumbnails_.begin();
auto full_message_id = it->second.full_message_id;
being_loaded_secret_thumbnails_.erase(it);
if (full_message_id.get_message_id().is_yet_unsent()) {
fail_send_message(full_message_id, Global::request_aborted_error());
}
}
while (!being_sent_messages_.empty()) {
on_send_message_fail(being_sent_messages_.begin()->first, Global::request_aborted_error());
}
}
fail_promises(load_active_live_location_messages_queries_, Global::request_aborted_error());
fail_promises(dialog_filter_reload_queries_, Global::request_aborted_error());
auto fail_promise_map = [](auto &queries) {
while (!queries.empty()) {
auto it = queries.begin();
auto promises = std::move(it->second);
queries.erase(it);
fail_promises(promises, Global::request_aborted_error());
}
};
fail_promise_map(get_dialog_queries_);
fail_promise_map(load_scheduled_messages_from_database_queries_);
fail_promise_map(run_after_get_channel_difference_);
fail_promise_map(search_public_dialogs_queries_);
while (!pending_channel_on_get_dialogs_.empty()) {
auto it = pending_channel_on_get_dialogs_.begin();
auto promise = std::move(it->second.promise);
pending_channel_on_get_dialogs_.erase(it);
promise.set_error(Global::request_aborted_error());
}
while (!get_dialogs_tasks_.empty()) {
auto it = get_dialogs_tasks_.begin();
auto promise = std::move(it->second.promise);
get_dialogs_tasks_.erase(it);
promise.set_error(Global::request_aborted_error());
}
stop();
}
void MessagesManager::start_up() {
init();
}
void MessagesManager::create_folders() {
LOG(INFO) << "Create folders";
dialog_folders_[FolderId::main()].folder_id = FolderId::main();
dialog_folders_[FolderId::archive()].folder_id = FolderId::archive();
add_dialog_list(DialogListId(FolderId::main()));
add_dialog_list(DialogListId(FolderId::archive()));
}
void MessagesManager::init() {
if (is_inited_) {
return;
}
is_inited_ = true;
td_->notification_settings_manager_->init(); // load scope notification settings
init_stickers_manager(td_); // load available reactions
always_wait_for_mailbox();
start_time_ = Time::now();
last_channel_pts_jump_warning_time_ = start_time_ - 3600;
bool is_authorized = td_->auth_manager_->is_authorized();
bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot();
if (was_authorized_user) {
create_folders(); // ensure that Main and Archive dialog lists are created
}
if (is_authorized && td_->auth_manager_->is_bot()) {
disable_get_dialog_filter_ = true;
}
authorization_date_ = td_->option_manager_->get_option_integer("authorization_date");
if (was_authorized_user) {
auto dialog_filters = G()->td_db()->get_binlog_pmc()->get("dialog_filters");
if (!dialog_filters.empty()) {
DialogFiltersLogEvent log_event;
if (log_event_parse(log_event, dialog_filters).is_ok()) {
server_main_dialog_list_position_ = log_event.server_main_dialog_list_position;
main_dialog_list_position_ = log_event.main_dialog_list_position;
if (!td_->option_manager_->get_option_boolean("is_premium") &&
(server_main_dialog_list_position_ != 0 || main_dialog_list_position_ != 0)) {
LOG(INFO) << "Ignore main chat list position " << server_main_dialog_list_position_ << '/'
<< main_dialog_list_position_;
server_main_dialog_list_position_ = 0;
main_dialog_list_position_ = 0;
}
dialog_filters_updated_date_ = G()->ignore_background_updates() ? 0 : log_event.updated_date;
std::unordered_set<DialogFilterId, DialogFilterIdHash> server_dialog_filter_ids;
for (auto &dialog_filter : log_event.server_dialog_filters_out) {
if (dialog_filter->dialog_filter_id.is_valid() &&
server_dialog_filter_ids.insert(dialog_filter->dialog_filter_id).second) {
server_dialog_filters_.push_back(std::move(dialog_filter));
}
}
for (auto &dialog_filter : log_event.dialog_filters_out) {
add_dialog_filter(std::move(dialog_filter), false, "binlog");
}
LOG(INFO) << "Loaded server chat filters "
<< get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
<< " and local chat filters " << get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
} else {
LOG(ERROR) << "Failed to parse chat filters from binlog";
}
}
send_update_chat_filters(); // always send updateChatFilters
}
if (G()->parameters().use_message_db && was_authorized_user) {
// erase old keys
G()->td_db()->get_binlog_pmc()->erase("last_server_dialog_date");
G()->td_db()->get_binlog_pmc()->erase("unread_message_count");
G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count");
auto last_database_server_dialog_dates = G()->td_db()->get_binlog_pmc()->prefix_get("last_server_dialog_date");
for (auto &it : last_database_server_dialog_dates) {
auto r_folder_id = to_integer_safe<int32>(it.first);
if (r_folder_id.is_error()) {
LOG(ERROR) << "Can't parse folder ID from " << it.first;
continue;
}
string order_str;
string dialog_id_str;
std::tie(order_str, dialog_id_str) = split(it.second);
auto r_order = to_integer_safe<int64>(order_str);
auto r_dialog_id = to_integer_safe<int64>(dialog_id_str);
if (r_order.is_error() || r_dialog_id.is_error()) {
LOG(ERROR) << "Can't parse " << it.second;
} else {
FolderId folder_id(r_folder_id.ok());
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
DialogDate dialog_date(r_order.ok(), DialogId(r_dialog_id.ok()));
if (dialog_date.get_date() == 0 && dialog_date != MAX_DIALOG_DATE) {
LOG(ERROR) << "Ignore incorrect last database server dialog date " << dialog_date << " in " << folder_id;
} else {
if (folder->last_database_server_dialog_date_ < dialog_date) {
folder->last_database_server_dialog_date_ = dialog_date;
}
LOG(INFO) << "Loaded last_database_server_dialog_date_ " << folder->last_database_server_dialog_date_
<< " in " << folder_id;
}
}
}
auto sponsored_dialog_id_string = G()->td_db()->get_binlog_pmc()->get("sponsored_dialog_id");
if (!sponsored_dialog_id_string.empty()) {
auto dialog_id_source = split(Slice(sponsored_dialog_id_string));
auto r_dialog_id = to_integer_safe<int64>(dialog_id_source.first);
auto r_source = DialogSource::unserialize(dialog_id_source.second);
if (r_dialog_id.is_error() || r_source.is_error()) {
LOG(ERROR) << "Can't parse " << sponsored_dialog_id_string;
} else {
DialogId dialog_id(r_dialog_id.ok());
const Dialog *d = get_dialog_force(dialog_id, "init");
if (d != nullptr) {
LOG(INFO) << "Loaded sponsored " << dialog_id;
add_sponsored_dialog(d, r_source.move_as_ok());
} else {
LOG(ERROR) << "Can't load " << dialog_id;
}
}
}
auto pinned_dialog_ids = G()->td_db()->get_binlog_pmc()->prefix_get("pinned_dialog_ids");
for (auto &it : pinned_dialog_ids) {
auto r_folder_id = to_integer_safe<int32>(it.first);
if (r_folder_id.is_error()) {
LOG(ERROR) << "Can't parse folder ID from " << it.first;
continue;
}
FolderId folder_id(r_folder_id.ok());
auto r_dialog_ids = transform(full_split(Slice(it.second), ','), [](Slice str) -> Result<DialogId> {
TRY_RESULT(dialog_id_int, to_integer_safe<int64>(str));
DialogId dialog_id(dialog_id_int);
if (!dialog_id.is_valid()) {
return Status::Error("Have invalid dialog ID");
}
return dialog_id;
});
if (std::any_of(r_dialog_ids.begin(), r_dialog_ids.end(),
[](auto &r_dialog_id) { return r_dialog_id.is_error(); })) {
LOG(ERROR) << "Can't parse " << it.second;
reload_pinned_dialogs(DialogListId(folder_id), Auto());
} else {
auto *list = get_dialog_list(DialogListId(folder_id));
CHECK(list != nullptr);
CHECK(list->pinned_dialogs_.empty());
for (auto &r_dialog_id : reversed(r_dialog_ids)) {
auto dialog_id = r_dialog_id.move_as_ok();
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Loaded " << dialog_id << " as a pinned dialog";
continue;
}
auto order = get_next_pinned_dialog_order();
list->pinned_dialogs_.emplace_back(order, dialog_id);
list->pinned_dialog_id_orders_.emplace(dialog_id, order);
}
std::reverse(list->pinned_dialogs_.begin(), list->pinned_dialogs_.end());
list->are_pinned_dialogs_inited_ = true;
update_list_last_pinned_dialog_date(*list);
LOG(INFO) << "Loaded pinned chats " << list->pinned_dialogs_ << " in " << folder_id;
}
}
auto unread_message_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_message_count");
for (auto &it : unread_message_counts) {
auto r_dialog_list_id = to_integer_safe<int64>(it.first);
if (r_dialog_list_id.is_error()) {
LOG(ERROR) << "Can't parse dialog list ID from " << it.first;
continue;
}
string total_count;
string muted_count;
std::tie(total_count, muted_count) = split(it.second);
auto r_total_count = to_integer_safe<int32>(total_count);
auto r_muted_count = to_integer_safe<int32>(muted_count);
if (r_total_count.is_error() || r_muted_count.is_error()) {
LOG(ERROR) << "Can't parse " << it.second;
} else {
DialogListId dialog_list_id(r_dialog_list_id.ok());
auto *list = get_dialog_list(dialog_list_id);
if (list != nullptr) {
list->unread_message_total_count_ = r_total_count.ok();
list->unread_message_muted_count_ = r_muted_count.ok();
list->is_message_unread_count_inited_ = true;
send_update_unread_message_count(*list, DialogId(), true, "load unread_message_count", true);
} else {
G()->td_db()->get_binlog_pmc()->erase("unread_message_count" + it.first);
}
}
}
auto unread_dialog_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_dialog_count");
for (auto &it : unread_dialog_counts) {
auto r_dialog_list_id = to_integer_safe<int64>(it.first);
if (r_dialog_list_id.is_error()) {
LOG(ERROR) << "Can't parse dialog list ID from " << it.first;
continue;
}
auto counts = transform(full_split(Slice(it.second)), [](Slice str) { return to_integer_safe<int32>(str); });
if ((counts.size() != 4 && counts.size() != 6) ||
std::any_of(counts.begin(), counts.end(), [](auto &c) { return c.is_error(); })) {
LOG(ERROR) << "Can't parse " << it.second;
} else {
DialogListId dialog_list_id(r_dialog_list_id.ok());
auto *list = get_dialog_list(dialog_list_id);
if (list != nullptr) {
list->unread_dialog_total_count_ = counts[0].ok();
list->unread_dialog_muted_count_ = counts[1].ok();
list->unread_dialog_marked_count_ = counts[2].ok();
list->unread_dialog_muted_marked_count_ = counts[3].ok();
if (counts.size() == 6) {
list->server_dialog_total_count_ = counts[4].ok();
list->secret_chat_total_count_ = counts[5].ok();
}
if (list->server_dialog_total_count_ == -1) {
repair_server_dialog_total_count(dialog_list_id);
}
if (list->secret_chat_total_count_ == -1) {
repair_secret_chat_total_count(dialog_list_id);
}
list->is_dialog_unread_count_inited_ = true;
send_update_unread_chat_count(*list, DialogId(), true, "load unread_dialog_count", true);
} else {
G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count" + it.first);
}
}
}
} else {
G()->td_db()->get_binlog_pmc()->erase_by_prefix("pinned_dialog_ids");
G()->td_db()->get_binlog_pmc()->erase_by_prefix("last_server_dialog_date");
G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_message_count");
G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_dialog_count");
G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id");
}
G()->td_db()->get_binlog_pmc()->erase("dialog_pinned_current_order");
if (G()->parameters().use_message_db) {
ttl_db_loop_start(G()->server_time());
}
load_calls_db_state();
if (was_authorized_user && is_authorized) {
if (need_synchronize_dialog_filters()) {
reload_dialog_filters();
} else {
auto cache_time = get_dialog_filters_cache_time();
schedule_dialog_filters_reload(cache_time - max(0, G()->unix_time() - dialog_filters_updated_date_));
}
}
auto auth_notification_ids_string = G()->td_db()->get_binlog_pmc()->get("auth_notification_ids");
if (!auth_notification_ids_string.empty()) {
VLOG(notifications) << "Loaded auth_notification_ids = " << auth_notification_ids_string;
auto ids = full_split(auth_notification_ids_string, ',');
CHECK(ids.size() % 2 == 0);
bool is_changed = false;
auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME;
for (size_t i = 0; i < ids.size(); i += 2) {
auto date = to_integer_safe<int32>(ids[i + 1]).ok();
if (date < min_date || ids[i].empty()) {
is_changed = true;
continue;
}
if (auth_notification_id_date_.size() == MAX_SAVED_AUTH_NOTIFICATION_IDS) {
is_changed = true;
break;
}
auth_notification_id_date_.emplace(std::move(ids[i]), date);
}
if (is_changed) {
save_auth_notification_ids();
}
}
/*
FI LE *f = std::f open("error.txt", "r");
if (f != nullptr) {
DialogId dialog_id(ChannelId(123456));
force_create_dialog(dialog_id, "test");
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_all_dialog_messages(d, true, false);
d->last_new_message_id = MessageId();
d->last_read_inbox_message_id = MessageId();
d->last_read_outbox_message_id = MessageId();
d->is_last_read_inbox_message_id_inited = false;
d->is_last_read_outbox_message_id_inited = false;
struct MessageBasicInfo {
MessageId message_id;
bool have_previous;
bool have_next;
};
vector<MessageBasicInfo> messages_info;
std::function<void(Message *m)> get_messages_info = [&](Message *m) {
if (m == nullptr) {
return;
}
get_messages_info(m->left.get());
messages_info.push_back(MessageBasicInfo{m->message_id, m->have_previous, m->have_next});
get_messages_info(m->right.get());
};
char buf[1280];
while (std::f gets(buf, sizeof(buf), f) != nullptr) {
Slice log_string(buf, std::strlen(buf));
Slice op = log_string.substr(0, log_string.find(' '));
if (op != "MessageOpAdd" && op != "MessageOpDelete") {
LOG(ERROR) << "Unsupported op " << op;
continue;
}
log_string.remove_prefix(log_string.find(' ') + 1);
if (!begins_with(log_string, "at ")) {
LOG(ERROR) << "Date expected, found " << log_string;
continue;
}
log_string.remove_prefix(3);
auto date_slice = log_string.substr(0, log_string.find(' '));
log_string.remove_prefix(date_slice.size());
bool is_server = false;
if (begins_with(log_string, " server message ")) {
log_string.remove_prefix(16);
is_server = true;
} else if (begins_with(log_string, " yet unsent message ")) {
log_string.remove_prefix(20);
} else if (begins_with(log_string, " local message ")) {
log_string.remove_prefix(15);
} else {
LOG(ERROR) << "Message identifier expected, found " << log_string;
continue;
}
auto server_message_id = to_integer<int32>(log_string);
auto add = 0;
if (!is_server) {
log_string.remove_prefix(log_string.find('.') + 1);
add = to_integer<int32>(log_string);
}
log_string.remove_prefix(log_string.find(' ') + 1);
auto message_id = MessageId(MessageId(ServerMessageId(server_message_id)).get() + add);
auto content_type = log_string.substr(0, log_string.find(' '));
log_string.remove_prefix(log_string.find(' ') + 1);
auto read_bool = [](Slice &str) {
if (begins_with(str, "true ")) {
str.remove_prefix(5);
return true;
}
if (begins_with(str, "false ")) {
str.remove_prefix(6);
return false;
}
LOG(ERROR) << "Bool expected, found " << str;
return false;
};
bool from_update = read_bool(log_string);
bool have_previous = read_bool(log_string);
bool have_next = read_bool(log_string);
if (op == "MessageOpAdd") {
auto m = make_unique<Message>();
set_message_id(m, message_id);
m->date = G()->unix_time();
m->content = create_text_message_content("text", {}, {});
m->have_previous = have_previous;
m->have_next = have_next;
bool need_update = from_update;
bool need_update_dialog_pos = false;
if (add_message_to_dialog(dialog_id, std::move(m), from_update, &need_update, &need_update_dialog_pos,
"Unknown source") == nullptr) {
LOG(ERROR) << "Can't add message " << message_id;
}
} else {
bool need_update_dialog_pos = false;
auto m = delete_message(d, message_id, true, &need_update_dialog_pos, "Unknown source");
CHECK(m != nullptr);
}
messages_info.clear();
get_messages_info(d->messages.get());
for (size_t i = 0; i + 1 < messages_info.size(); i++) {
if (messages_info[i].have_next != messages_info[i + 1].have_previous) {
LOG(ERROR) << messages_info[i].message_id << " has have_next = " << messages_info[i].have_next << ", but "
<< messages_info[i + 1].message_id
<< " has have_previous = " << messages_info[i + 1].have_previous;
}
}
if (!messages_info.empty()) {
if (messages_info.back().have_next != false) {
LOG(ERROR) << messages_info.back().message_id << " has have_next = true, but there is no next message";
}
if (messages_info[0].have_previous != false) {
LOG(ERROR) << messages_info[0].message_id << " has have_previous = true, but there is no previous message";
}
}
}
messages_info.clear();
get_messages_info(d->messages.get());
for (auto &info : messages_info) {
bool need_update_dialog_pos = false;
auto m = delete_message(d, info.message_id, true, &need_update_dialog_pos, "Unknown source");
CHECK(m != nullptr);
}
std::f close(f);
}
*/
}
void MessagesManager::on_authorization_success() {
CHECK(td_->auth_manager_->is_authorized());
authorization_date_ = td_->option_manager_->get_option_integer("authorization_date");
if (td_->auth_manager_->is_bot()) {
disable_get_dialog_filter_ = true;
return;
}
create_folders();
reload_dialog_filters();
}
void MessagesManager::ttl_db_loop_start(double server_now) {
ttl_db_expires_from_ = 0;
ttl_db_expires_till_ = static_cast<int32>(server_now) + 15 /* 15 seconds */;
ttl_db_has_query_ = false;
ttl_db_loop(server_now);
}
void MessagesManager::ttl_db_loop(double server_now) {
LOG(INFO) << "Begin ttl_db loop: " << tag("expires_from", ttl_db_expires_from_)
<< tag("expires_till", ttl_db_expires_till_) << tag("has_query", ttl_db_has_query_);
if (ttl_db_has_query_) {
return;
}
auto now = static_cast<int32>(server_now);
if (ttl_db_expires_till_ < 0) {
LOG(INFO) << "Finish ttl_db loop";
return;
}
if (now < ttl_db_expires_from_) {
ttl_db_slot_.set_event(EventCreator::yield(actor_shared(this, YieldType::TtlDb)));
auto wakeup_in = ttl_db_expires_from_ - server_now;
ttl_db_slot_.set_timeout_in(wakeup_in);
LOG(INFO) << "Set ttl_db timeout in " << wakeup_in;
return;
}
ttl_db_has_query_ = true;
int32 limit = 50;
LOG(INFO) << "Send ttl_db query " << tag("expires_from", ttl_db_expires_from_)
<< tag("expires_till", ttl_db_expires_till_) << tag("limit", limit);
G()->td_db()->get_message_db_async()->get_expiring_messages(
ttl_db_expires_from_, ttl_db_expires_till_, limit,
PromiseCreator::lambda(
[actor_id = actor_id(this)](Result<std::pair<std::vector<MessageDbMessage>, int32>> result) {
send_closure(actor_id, &MessagesManager::ttl_db_on_result, std::move(result), false);
}));
}
void MessagesManager::ttl_db_on_result(Result<std::pair<std::vector<MessageDbMessage>, int32>> r_result, bool dummy) {
if (G()->close_flag()) {
return;
}
CHECK(r_result.is_ok());
auto result = r_result.move_as_ok();
ttl_db_has_query_ = false;
ttl_db_expires_from_ = ttl_db_expires_till_;
ttl_db_expires_till_ = result.second;
LOG(INFO) << "Receive ttl_db query result " << tag("new expires_till", ttl_db_expires_till_)
<< tag("got messages", result.first.size());
for (auto &dialog_message : result.first) {
on_get_message_from_database(dialog_message, false, "ttl_db_on_result");
}
ttl_db_loop(G()->server_time());
}
void MessagesManager::on_send_secret_message_error(int64 random_id, Status error, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after error is saved
auto it = being_sent_messages_.find(random_id);
if (it != being_sent_messages_.end()) {
auto full_message_id = it->second;
auto *m = get_message(full_message_id);
if (m != nullptr) {
auto file_id = get_message_content_upload_file_id(m->content.get());
if (file_id.is_valid()) {
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
if (begins_with(error.message(), "FILE_PART_") && ends_with(error.message(), "_MISSING")) {
on_send_message_file_part_missing(random_id, to_integer<int32>(error.message().substr(10)));
return;
}
if (error.code() != 429 && error.code() < 500 && !G()->close_flag()) {
td_->file_manager_->delete_partial_remote_location(file_id);
}
}
}
}
on_send_message_fail(random_id, std::move(error));
}
void MessagesManager::on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date,
unique_ptr<EncryptedFile> file, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after message is saved
FileId new_file_id;
if (file != nullptr) {
if (!DcId::is_valid(file->dc_id_)) {
LOG(ERROR) << "Wrong dc_id = " << file->dc_id_ << " in file " << *file;
} else {
DialogId owner_dialog_id;
auto it = being_sent_messages_.find(random_id);
if (it != being_sent_messages_.end()) {
owner_dialog_id = it->second.get_dialog_id();
}
new_file_id = td_->file_manager_->register_remote(
FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::internal(file->dc_id_), ""),
FileLocationSource::FromServer, owner_dialog_id, 0, file->size_, to_string(static_cast<uint64>(file->id_)));
}
}
on_send_message_success(random_id, message_id, date, 0, new_file_id, "on_send_secret_message_success");
}
void MessagesManager::delete_secret_messages(SecretChatId secret_chat_id, std::vector<int64> random_ids,
Promise<Unit> promise) {
LOG(DEBUG) << "On delete messages in " << secret_chat_id << " with random_ids " << random_ids;
CHECK(secret_chat_id.is_valid());
DialogId dialog_id(secret_chat_id);
if (!have_dialog_force(dialog_id, "delete_secret_messages")) {
LOG(ERROR) << "Ignore delete secret messages in unknown " << dialog_id;
promise.set_value(Unit());
return;
}
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
pending_secret_message->type = PendingSecretMessage::Type::DeleteMessages;
pending_secret_message->dialog_id = dialog_id;
pending_secret_message->random_ids = std::move(random_ids);
add_secret_message(std::move(pending_secret_message));
}
void MessagesManager::finish_delete_secret_messages(DialogId dialog_id, std::vector<int64> random_ids,
Promise<Unit> promise) {
LOG(INFO) << "Delete messages with random_ids " << random_ids << " in " << dialog_id;
promise.set_value(Unit()); // TODO: set after event is saved
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
vector<MessageId> to_delete_message_ids;
for (auto &random_id : random_ids) {
auto message_id = get_message_id_by_random_id(d, random_id, "finish_delete_secret_messages");
if (!message_id.is_valid()) {
LOG(INFO) << "Can't find message with random_id " << random_id;
continue;
}
const Message *m = get_message(d, message_id);
CHECK(m != nullptr);
if (!is_service_message_content(m->content->get_type())) {
to_delete_message_ids.push_back(message_id);
} else {
LOG(INFO) << "Skip deletion of service " << message_id;
}
}
delete_dialog_messages(d, to_delete_message_ids, true, "finish_delete_secret_messages");
}
void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list,
MessageId last_message_id, Promise<Unit> promise) {
LOG(DEBUG) << "Delete history in " << secret_chat_id << " up to " << last_message_id;
CHECK(secret_chat_id.is_valid());
CHECK(!last_message_id.is_scheduled());
DialogId dialog_id(secret_chat_id);
if (!have_dialog_force(dialog_id, "delete_secret_chat_history")) {
LOG(ERROR) << "Ignore delete history in unknown " << dialog_id;
promise.set_value(Unit());
return;
}
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
pending_secret_message->type = PendingSecretMessage::Type::DeleteHistory;
pending_secret_message->dialog_id = dialog_id;
pending_secret_message->last_message_id = last_message_id;
pending_secret_message->remove_from_dialog_list = remove_from_dialog_list;
add_secret_message(std::move(pending_secret_message));
}
void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list,
MessageId last_message_id, Promise<Unit> promise) {
LOG(DEBUG) << "Delete history in " << dialog_id << " up to " << last_message_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
// TODO: probably last_message_id is not needed
delete_all_dialog_messages(d, remove_from_dialog_list, true);
promise.set_value(Unit()); // TODO: set after event is saved
}
void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date) {
if (!secret_chat_id.is_valid()) {
LOG(ERROR) << "Receive read secret chat outbox in the invalid " << secret_chat_id;
return;
}
auto dialog_id = DialogId(secret_chat_id);
Dialog *d = get_dialog_force(dialog_id, "read_secret_chat_outbox");
if (d == nullptr) {
return;
}
if (read_date > 0) {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
if (user_id.is_valid()) {
td_->contacts_manager_->on_update_user_local_was_online(user_id, read_date);
}
}
// TODO: protect with log event
suffix_load_till_date(
d, up_to_date,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, up_to_date, read_date](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::read_secret_chat_outbox_inner, dialog_id, up_to_date, read_date);
}));
}
void MessagesManager::read_secret_chat_outbox_inner(DialogId dialog_id, int32 up_to_date, int32 read_date) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto end = MessagesConstIterator(d, MessageId::max());
while (*end && ((*end)->date > up_to_date || (*end)->message_id.is_yet_unsent())) {
--end;
}
if (!*end) {
LOG(INFO) << "Ignore read_secret_chat_outbox in " << dialog_id << " at " << up_to_date
<< ": no messages with such date are known";
return;
}
auto max_message_id = (*end)->message_id;
read_history_outbox(dialog_id, max_message_id, read_date);
}
void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after event is saved
DialogId dialog_id(secret_chat_id);
Dialog *d = get_dialog_force(dialog_id, "open_secret_message");
if (d == nullptr) {
LOG(ERROR) << "Ignore opening secret chat message in unknown " << dialog_id;
return;
}
auto message_id = get_message_id_by_random_id(d, random_id, "open_secret_message");
if (!message_id.is_valid()) {
return;
}
Message *m = get_message(d, message_id);
CHECK(m != nullptr);
if (m->message_id.is_yet_unsent() || m->is_failed_to_send || !m->is_outgoing) {
LOG(ERROR) << "Peer has opened wrong " << message_id << " in " << dialog_id;
return;
}
read_message_content(d, m, false, "open_secret_message");
}
void MessagesManager::on_update_secret_chat_state(SecretChatId secret_chat_id, SecretChatState state) {
if (state == SecretChatState::Closed && !td_->auth_manager_->is_bot()) {
DialogId dialog_id(secret_chat_id);
Dialog *d = get_dialog_force(dialog_id, "on_update_secret_chat_state");
if (d != nullptr) {
if (d->new_secret_chat_notification_id.is_valid()) {
remove_new_secret_chat_notification(d, true);
}
if (d->message_notification_group.group_id.is_valid() && get_dialog_pending_notification_count(d, false) == 0 &&
!d->message_notification_group.last_notification_id.is_valid()) {
CHECK(d->message_notification_group.last_notification_date == 0);
d->message_notification_group.try_reuse = true;
d->message_notification_group.is_changed = true;
on_dialog_updated(d->dialog_id, "on_update_secret_chat_state");
}
CHECK(!d->mention_notification_group.group_id.is_valid()); // there can't be unread mentions in secret chats
}
}
}
void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
int32 date, unique_ptr<EncryptedFile> file,
tl_object_ptr<secret_api::decryptedMessage> message,
Promise<Unit> promise) {
LOG(DEBUG) << "On get " << to_string(message);
CHECK(message != nullptr);
CHECK(secret_chat_id.is_valid());
CHECK(user_id.is_valid());
CHECK(message_id.is_valid());
CHECK(date > 0);
if (message->random_id_ == 0) {
LOG(ERROR) << "Ignore secret message with random_id == 0";
promise.set_error(Status::Error(400, "Invalid random_id"));
return;
}
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
MessageInfo &message_info = pending_secret_message->message_info;
message_info.dialog_id = DialogId(secret_chat_id);
message_info.message_id = message_id;
message_info.sender_user_id = user_id;
message_info.date = date;
message_info.random_id = message->random_id_;
message_info.ttl = message->ttl_;
Dialog *d = get_dialog_force(message_info.dialog_id, "on_get_secret_message");
if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
d = get_dialog(message_info.dialog_id);
}
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
return;
}
pending_secret_message_ids_[message_info.dialog_id][message_info.random_id] = message_id;
pending_secret_message->load_data_multipromise.add_promise(Auto());
auto lock_promise = pending_secret_message->load_data_multipromise.get_promise();
int32 flags = MESSAGE_FLAG_HAS_UNREAD_CONTENT | MESSAGE_FLAG_HAS_FROM_ID;
if ((message->flags_ & secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK) != 0) {
message_info.reply_header.reply_to_message_id =
get_message_id_by_random_id(d, message->reply_to_random_id_, "on_get_secret_message");
if (!message_info.reply_header.reply_to_message_id.is_valid()) {
auto dialog_it = pending_secret_message_ids_.find(message_info.dialog_id);
if (dialog_it != pending_secret_message_ids_.end()) {
auto message_it = dialog_it->second.find(message->reply_to_random_id_);
if (message_it != dialog_it->second.end()) {
message_info.reply_header.reply_to_message_id = message_it->second;
}
}
}
}
if ((message->flags_ & secret_api::decryptedMessage::SILENT_MASK) != 0) {
flags |= MESSAGE_FLAG_IS_SILENT;
}
if (!clean_input_string(message->via_bot_name_)) {
LOG(WARNING) << "Receive invalid bot username " << message->via_bot_name_;
message->via_bot_name_.clear();
}
if (!message->via_bot_name_.empty()) {
auto request_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), via_bot_username = message->via_bot_name_, message_info_ptr = &message_info,
promise = pending_secret_message->load_data_multipromise.get_promise()](Unit) mutable {
send_closure(actor_id, &MessagesManager::on_resolve_secret_chat_message_via_bot_username, via_bot_username,
message_info_ptr, std::move(promise));
});
search_public_dialog(message->via_bot_name_, false, std::move(request_promise));
}
if ((message->flags_ & secret_api::decryptedMessage::GROUPED_ID_MASK) != 0 && message->grouped_id_ != 0) {
message_info.media_album_id = message->grouped_id_;
}
message_info.flags = flags;
message_info.content = get_secret_message_content(
td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_),
message_info.dialog_id, pending_secret_message->load_data_multipromise,
td_->contacts_manager_->is_user_premium(user_id));
add_secret_message(std::move(pending_secret_message), std::move(lock_promise));
}
void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username,
MessageInfo *message_info_ptr,
Promise<Unit> &&promise) {
if (!G()->close_flag()) {
auto dialog_id = resolve_dialog_username(via_bot_username);
if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) {
auto user_id = dialog_id.get_user_id();
auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id);
if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) {
message_info_ptr->flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT;
message_info_ptr->via_bot_user_id = user_id;
}
}
}
promise.set_value(Unit());
}
void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
int32 date, int64 random_id, Promise<Unit> promise) {
LOG(DEBUG) << "On screenshot taken in " << secret_chat_id;
CHECK(secret_chat_id.is_valid());
CHECK(user_id.is_valid());
CHECK(message_id.is_valid());
CHECK(date > 0);
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
MessageInfo &message_info = pending_secret_message->message_info;
message_info.dialog_id = DialogId(secret_chat_id);
message_info.message_id = message_id;
message_info.sender_user_id = user_id;
message_info.date = date;
message_info.random_id = random_id;
message_info.flags = MESSAGE_FLAG_HAS_FROM_ID;
message_info.content = create_screenshot_taken_message_content();
Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_screenshot_taken");
if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
d = get_dialog(message_info.dialog_id);
}
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
return;
}
add_secret_message(std::move(pending_secret_message));
}
void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
int32 date, int32 ttl, int64 random_id, Promise<Unit> promise) {
LOG(DEBUG) << "On TTL set in " << secret_chat_id << " to " << ttl;
CHECK(secret_chat_id.is_valid());
CHECK(user_id.is_valid());
CHECK(message_id.is_valid());
CHECK(date > 0);
if (ttl < 0) {
LOG(WARNING) << "Receive wrong TTL = " << ttl;
promise.set_value(Unit());
return;
}
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
MessageInfo &message_info = pending_secret_message->message_info;
message_info.dialog_id = DialogId(secret_chat_id);
message_info.message_id = message_id;
message_info.sender_user_id = user_id;
message_info.date = date;
message_info.random_id = random_id;
message_info.flags = MESSAGE_FLAG_HAS_FROM_ID;
message_info.content = create_chat_set_ttl_message_content(ttl, UserId());
Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_ttl_changed");
if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
d = get_dialog(message_info.dialog_id);
}
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
return;
}
add_secret_message(std::move(pending_secret_message));
}
void MessagesManager::add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message,
Promise<Unit> lock_promise) {
auto &multipromise = pending_secret_message->load_data_multipromise;
multipromise.set_ignore_errors(true);
int64 token = pending_secret_messages_.add(std::move(pending_secret_message));
multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), token](Result<Unit> result) {
if (result.is_ok()) {
send_closure(actor_id, &MessagesManager::on_add_secret_message_ready, token);
}
}));
if (!lock_promise) {
lock_promise = multipromise.get_promise();
}
lock_promise.set_value(Unit());
}
void MessagesManager::on_add_secret_message_ready(int64 token) {
if (G()->close_flag()) {
return;
}
pending_secret_messages_.finish(
token, [actor_id = actor_id(this)](unique_ptr<PendingSecretMessage> pending_secret_message) {
send_closure_later(actor_id, &MessagesManager::finish_add_secret_message, std::move(pending_secret_message));
});
}
void MessagesManager::finish_add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message) {
if (G()->close_flag()) {
return;
}
if (pending_secret_message->type == PendingSecretMessage::Type::DeleteMessages) {
return finish_delete_secret_messages(pending_secret_message->dialog_id,
std::move(pending_secret_message->random_ids),
std::move(pending_secret_message->success_promise));
}
if (pending_secret_message->type == PendingSecretMessage::Type::DeleteHistory) {
return finish_delete_secret_chat_history(
pending_secret_message->dialog_id, pending_secret_message->remove_from_dialog_list,
pending_secret_message->last_message_id, std::move(pending_secret_message->success_promise));
}
auto d = get_dialog(pending_secret_message->message_info.dialog_id);
CHECK(d != nullptr);
auto random_id = pending_secret_message->message_info.random_id;
auto message_id = get_message_id_by_random_id(d, random_id, "finish_add_secret_message");
if (message_id.is_valid()) {
if (message_id != pending_secret_message->message_info.message_id) {
LOG(WARNING) << "Ignore duplicate " << pending_secret_message->message_info.message_id
<< " received earlier with " << message_id << " and random_id " << random_id;
}
} else {
if (!td_->contacts_manager_->is_user_premium(pending_secret_message->message_info.sender_user_id)) {
auto message_text = get_message_content_text_mutable(pending_secret_message->message_info.content.get());
if (message_text != nullptr) {
remove_premium_custom_emoji_entities(td_, message_text->entities, true);
}
}
on_get_message(std::move(pending_secret_message->message_info), true, false, true, true,
"finish add secret message");
}
auto dialog_it = pending_secret_message_ids_.find(d->dialog_id);
if (dialog_it != pending_secret_message_ids_.end()) {
auto message_it = dialog_it->second.find(random_id);
if (message_it != dialog_it->second.end() && message_it->second == message_id) {
dialog_it->second.erase(message_it);
if (dialog_it->second.empty()) {
pending_secret_message_ids_.erase(dialog_it);
}
}
}
pending_secret_message->success_promise.set_value(Unit()); // TODO: set after message is saved
}
MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message(
tl_object_ptr<telegram_api::Message> message_ptr, bool is_scheduled, const char *source) const {
LOG(DEBUG) << "Receive from " << source << " " << to_string(message_ptr);
LOG_CHECK(message_ptr != nullptr) << source;
MessageInfo message_info;
message_info.message_id = MessageId::get_message_id(message_ptr, is_scheduled);
switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID:
message_info.message_id = MessageId();
break;
case telegram_api::message::ID: {
auto message = move_tl_object_as<telegram_api::message>(message_ptr);
message_info.dialog_id = DialogId(message->peer_id_);
if (message->from_id_ != nullptr) {
message_info.sender_dialog_id = DialogId(message->from_id_);
} else {
message_info.sender_dialog_id = message_info.dialog_id;
}
message_info.date = message->date_;
message_info.forward_header = std::move(message->fwd_from_);
bool can_have_thread = !is_scheduled && message_info.dialog_id.get_type() == DialogType::Channel &&
!is_broadcast_channel(message_info.dialog_id);
message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id,
message_info.message_id, message_info.date, can_have_thread);
if (message->flags_ & MESSAGE_FLAG_IS_SENT_VIA_BOT) {
message_info.via_bot_user_id = UserId(message->via_bot_id_);
if (!message_info.via_bot_user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << message_info.via_bot_user_id << " from " << source;
message_info.via_bot_user_id = UserId();
}
}
if (message->flags_ & MESSAGE_FLAG_HAS_INTERACTION_INFO) {
message_info.view_count = message->views_;
message_info.forward_count = message->forwards_;
}
if (message->flags_ & MESSAGE_FLAG_HAS_REPLY_INFO) {
message_info.reply_info = std::move(message->replies_);
}
if (message->flags_ & MESSAGE_FLAG_HAS_REACTIONS) {
message_info.reactions = std::move(message->reactions_);
}
if (message->flags_ & MESSAGE_FLAG_HAS_EDIT_DATE) {
message_info.edit_date = message->edit_date_;
}
if (message->flags_ & MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID) {
message_info.media_album_id = message->grouped_id_;
}
if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) {
message_info.ttl_period = message->ttl_period_;
}
message_info.flags = message->flags_;
bool is_content_read = (message->flags_ & MESSAGE_FLAG_HAS_UNREAD_CONTENT) == 0;
if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0)) {
is_content_read = true;
}
if (is_scheduled) {
is_content_read = false;
}
auto new_source = PSTRING() << FullMessageId(message_info.dialog_id, message_info.message_id) << " sent by "
<< message_info.sender_dialog_id << " from " << source;
message_info.content = get_message_content(
td_,
get_message_text(td_->contacts_manager_.get(), std::move(message->message_), std::move(message->entities_),
true, td_->auth_manager_->is_bot(),
message_info.forward_header ? message_info.forward_header->date_ : message_info.date,
message_info.media_album_id != 0, new_source.c_str()),
std::move(message->media_), message_info.dialog_id, is_content_read, message_info.via_bot_user_id,
&message_info.ttl, &message_info.disable_web_page_preview, new_source.c_str());
message_info.reply_markup = std::move(message->reply_markup_);
message_info.restriction_reasons = get_restriction_reasons(std::move(message->restriction_reason_));
message_info.author_signature = std::move(message->post_author_);
break;
}
case telegram_api::messageService::ID: {
auto message = move_tl_object_as<telegram_api::messageService>(message_ptr);
message_info.dialog_id = DialogId(message->peer_id_);
if (message->from_id_ != nullptr) {
message_info.sender_dialog_id = DialogId(message->from_id_);
} else {
message_info.sender_dialog_id = message_info.dialog_id;
}
message_info.date = message->date_;
if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) {
message_info.ttl_period = message->ttl_period_;
}
message_info.flags = message->flags_;
bool can_have_thread = !is_scheduled && message_info.dialog_id.get_type() == DialogType::Channel &&
!is_broadcast_channel(message_info.dialog_id);
message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id,
message_info.message_id, message_info.date, can_have_thread);
message_info.content = get_action_message_content(td_, std::move(message->action_), message_info.dialog_id,
message_info.reply_header.reply_in_dialog_id,
message_info.reply_header.reply_to_message_id);
message_info.reply_header.reply_in_dialog_id = DialogId();
message_info.reply_header.reply_to_message_id = MessageId();
break;
}
default:
UNREACHABLE();
break;
}
if (message_info.sender_dialog_id.is_valid() && message_info.sender_dialog_id.get_type() == DialogType::User) {
message_info.sender_user_id = message_info.sender_dialog_id.get_user_id();
message_info.sender_dialog_id = DialogId();
}
return message_info;
}
std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::create_message(MessageInfo &&message_info,
bool is_channel_message) {
DialogId dialog_id = message_info.dialog_id;
MessageId message_id = message_info.message_id;
if ((!message_id.is_valid() && !message_id.is_valid_scheduled()) || !dialog_id.is_valid()) {
if (message_id != MessageId() || dialog_id != DialogId()) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id;
}
return {DialogId(), nullptr};
}
if (message_id.is_yet_unsent() || message_id.is_local()) {
LOG(ERROR) << "Receive " << message_id;
return {DialogId(), nullptr};
}
CHECK(message_info.content != nullptr);
auto dialog_type = dialog_id.get_type();
UserId sender_user_id = message_info.sender_user_id;
DialogId sender_dialog_id = message_info.sender_dialog_id;
if (!sender_user_id.is_valid()) {
if (sender_user_id != UserId()) {
LOG(ERROR) << "Receive invalid " << sender_user_id;
sender_user_id = UserId();
}
if (!is_broadcast_channel(dialog_id) && td_->auth_manager_->is_bot()) {
if (dialog_id == sender_dialog_id) {
td_->contacts_manager_->add_anonymous_bot_user();
} else {
td_->contacts_manager_->add_service_notifications_user();
td_->contacts_manager_->add_channel_bot_user();
}
}
}
if (sender_dialog_id.is_valid()) {
if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
LOG(ERROR) << "Receive " << message_id << " sent by " << sender_dialog_id << " in " << dialog_id;
return {DialogId(), nullptr};
}
} else if (sender_dialog_id != DialogId()) {
LOG(ERROR) << "Receive invalid " << sender_dialog_id;
sender_dialog_id = DialogId();
}
if (message_id.is_scheduled()) {
is_channel_message = (dialog_type == DialogType::Channel);
}
int32 flags = message_info.flags;
bool is_outgoing = (flags & MESSAGE_FLAG_IS_OUT) != 0;
bool is_silent = (flags & MESSAGE_FLAG_IS_SILENT) != 0;
bool is_channel_post = (flags & MESSAGE_FLAG_IS_POST) != 0;
bool is_legacy = (flags & MESSAGE_FLAG_IS_LEGACY) != 0;
bool hide_edit_date = (flags & MESSAGE_FLAG_HIDE_EDIT_DATE) != 0;
bool is_from_scheduled = (flags & MESSAGE_FLAG_IS_FROM_SCHEDULED) != 0;
bool is_pinned = (flags & MESSAGE_FLAG_IS_PINNED) != 0;
bool noforwards = (flags & MESSAGE_FLAG_NOFORWARDS) != 0;
LOG_IF(ERROR, is_channel_message != (dialog_type == DialogType::Channel))
<< "Receive wrong is_channel_message for " << message_id << " in " << dialog_id;
if (is_channel_post && !is_broadcast_channel(dialog_id)) {
LOG(ERROR) << "Receive is_channel_post for " << message_id << " in " << dialog_id;
is_channel_post = false;
}
UserId my_id = td_->contacts_manager_->get_my_id();
DialogId my_dialog_id = DialogId(my_id);
if (dialog_id == my_dialog_id && (sender_user_id != my_id || sender_dialog_id.is_valid())) {
LOG(ERROR) << "Receive " << sender_user_id << "/" << sender_dialog_id << " as a sender of " << message_id
<< " instead of self";
sender_user_id = my_id;
sender_dialog_id = DialogId();
}
bool supposed_to_be_outgoing = sender_user_id == my_id && !(dialog_id == my_dialog_id && !message_id.is_scheduled());
if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing) {
LOG(ERROR) << "Receive wrong message out flag: me is " << my_id << ", message is from " << sender_user_id
<< ", flags = " << flags << " for " << message_id << " in " << dialog_id;
is_outgoing = supposed_to_be_outgoing;
/*
// it is useless to call getChannelDifference, because the channel pts will be increased already
if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) &&
get_channel_difference_to_log_event_id_.count(dialog_id) == 0) {
// it is safer to completely ignore the message and re-get it through getChannelDifference
Dialog *d = get_dialog(dialog_id);
if (d != nullptr) {
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
return {DialogId(), nullptr};
}
}
*/
}
int32 date = message_info.date;
if (date <= 0) {
LOG(ERROR) << "Wrong date = " << date << " received in " << message_id << " in " << dialog_id;
date = 1;
}
MessageId reply_to_message_id = message_info.reply_header.reply_to_message_id;
DialogId reply_in_dialog_id = message_info.reply_header.reply_in_dialog_id;
MessageId top_thread_message_id = message_info.reply_header.top_thread_message_id;
bool is_topic_message = message_info.reply_header.is_topic_message;
fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, reply_to_message_id);
fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, top_thread_message_id);
UserId via_bot_user_id = message_info.via_bot_user_id;
if (!via_bot_user_id.is_valid()) {
via_bot_user_id = UserId();
}
int32 edit_date = message_info.edit_date;
if (edit_date < 0) {
LOG(ERROR) << "Wrong edit_date = " << edit_date << " received in " << message_id << " in " << dialog_id;
edit_date = 0;
}
auto content_type = message_info.content->get_type();
if (content_type == MessageContentType::Sticker &&
get_message_content_sticker_type(td_, message_info.content.get()) == StickerType::CustomEmoji) {
LOG(INFO) << "Replace emoji sticker with an empty message";
message_info.content = create_text_message_content("Invalid sticker", {}, WebPageId());
content_type = message_info.content->get_type();
}
if (hide_edit_date && td_->auth_manager_->is_bot()) {
hide_edit_date = false;
}
if (hide_edit_date && content_type == MessageContentType::LiveLocation) {
hide_edit_date = false;
}
int32 ttl_period = message_info.ttl_period;
if (ttl_period < 0 || (message_id.is_scheduled() && ttl_period != 0)) {
LOG(ERROR) << "Wrong TTL period = " << ttl_period << " received in " << message_id << " in " << dialog_id;
ttl_period = 0;
}
int32 ttl = message_info.ttl;
bool is_content_secret = is_secret_message_content(ttl, content_type); // must be calculated before TTL is adjusted
if (ttl < 0 || (message_id.is_scheduled() && ttl != 0)) {
LOG(ERROR) << "Wrong TTL = " << ttl << " received in " << message_id << " in " << dialog_id;
ttl = 0;
} else if (ttl > 0) {
ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1);
}
if (message_id.is_scheduled()) {
if (message_info.reply_info != nullptr) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reply info";
message_info.reply_info = nullptr;
}
if (message_info.reactions != nullptr) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reactions";
message_info.reactions = nullptr;
}
}
int32 view_count = message_info.view_count;
if (view_count < 0) {
LOG(ERROR) << "Wrong view_count = " << view_count << " received in " << message_id << " in " << dialog_id;
view_count = 0;
}
int32 forward_count = message_info.forward_count;
if (forward_count < 0) {
LOG(ERROR) << "Wrong forward_count = " << forward_count << " received in " << message_id << " in " << dialog_id;
forward_count = 0;
}
MessageReplyInfo reply_info(td_, std::move(message_info.reply_info), td_->auth_manager_->is_bot());
if (!top_thread_message_id.is_valid() && is_thread_message(dialog_id, message_id, reply_info, content_type)) {
top_thread_message_id = message_id;
is_topic_message = (content_type == MessageContentType::TopicCreate);
}
if (top_thread_message_id.is_valid() && dialog_type != DialogType::Channel) {
// just in case
top_thread_message_id = MessageId();
}
if (!top_thread_message_id.is_valid()) {
// just in case
is_topic_message = false;
}
auto reactions =
MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot());
if (reactions != nullptr) {
reactions->sort_reactions(active_reaction_pos_);
reactions->fix_chosen_reaction(get_my_dialog_id());
}
bool has_forward_info = message_info.forward_header != nullptr;
if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && have_dialog_info_force(sender_dialog_id)) {
force_create_dialog(sender_dialog_id, "create_message", sender_dialog_id.get_type() != DialogType::User);
}
LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id;
auto message = make_unique<Message>();
set_message_id(message, message_id);
message->sender_user_id = sender_user_id;
message->sender_dialog_id = sender_dialog_id;
message->date = date;
message->ttl_period = ttl_period;
message->ttl = ttl;
message->disable_web_page_preview = message_info.disable_web_page_preview;
message->edit_date = edit_date;
message->random_id = message_info.random_id;
message->forward_info = get_message_forward_info(std::move(message_info.forward_header), {dialog_id, message_id});
message->reply_to_message_id = reply_to_message_id;
message->reply_in_dialog_id = reply_in_dialog_id;
message->top_thread_message_id = top_thread_message_id;
message->is_topic_message = is_topic_message;
message->via_bot_user_id = via_bot_user_id;
message->restriction_reasons = std::move(message_info.restriction_reasons);
message->author_signature = std::move(message_info.author_signature);
message->is_outgoing = is_outgoing;
message->is_channel_post = is_channel_post;
message->contains_mention =
!is_outgoing && dialog_type != DialogType::User &&
((flags & MESSAGE_FLAG_HAS_MENTION) != 0 || content_type == MessageContentType::PinMessage);
message->contains_unread_mention =
!message_id.is_scheduled() && message_id.is_server() && message->contains_mention &&
(flags & MESSAGE_FLAG_HAS_UNREAD_CONTENT) != 0 &&
(dialog_type == DialogType::Chat || (dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id)));
message->disable_notification = is_silent;
message->is_content_secret = is_content_secret;
message->hide_edit_date = hide_edit_date;
message->is_from_scheduled = is_from_scheduled;
message->is_pinned = is_pinned;
message->noforwards = noforwards;
message->interaction_info_update_date = G()->unix_time();
message->view_count = view_count;
message->forward_count = forward_count;
message->reply_info = std::move(reply_info);
message->reactions = std::move(reactions);
message->legacy_layer = (is_legacy ? MTPROTO_LAYER : 0);
message->content = std::move(message_info.content);
message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false,
message->contains_mention || dialog_type == DialogType::User);
if (content_type == MessageContentType::ExpiredPhoto || content_type == MessageContentType::ExpiredVideo) {
CHECK(message->ttl == 0); // TTL is ignored/set to 0 if the message has already been expired
if (message->reply_markup != nullptr) {
if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
message->had_reply_markup = true;
}
message->reply_markup = nullptr;
}
message->reply_to_message_id = MessageId();
message->reply_to_random_id = 0;
message->reply_in_dialog_id = DialogId();
message->linked_top_thread_message_id = MessageId();
}
if (message_info.media_album_id != 0) {
if (!is_allowed_media_group_content(content_type)) {
if (content_type != MessageContentType::Unsupported) {
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, false, -1)));
}
} else {
message->media_album_id = message_info.media_album_id;
}
}
if (message->forward_info == nullptr && has_forward_info) {
message->had_forward_info = true;
}
return {dialog_id, std::move(message)};
}
MessageId MessagesManager::find_old_message_id(DialogId dialog_id, MessageId message_id) const {
if (message_id.is_scheduled()) {
CHECK(message_id.is_scheduled_server());
auto dialog_it = update_scheduled_message_ids_.find(dialog_id);
if (dialog_it != update_scheduled_message_ids_.end()) {
auto it = dialog_it->second.find(message_id.get_scheduled_server_message_id());
if (it != dialog_it->second.end()) {
return it->second;
}
}
} else {
CHECK(message_id.is_server());
auto it = update_message_ids_.find(FullMessageId(dialog_id, message_id));
if (it != update_message_ids_.end()) {
return it->second;
}
}
return MessageId();
}
void MessagesManager::delete_update_message_id(DialogId dialog_id, MessageId message_id) {
if (message_id.is_scheduled()) {
CHECK(message_id.is_scheduled_server());
auto dialog_it = update_scheduled_message_ids_.find(dialog_id);
CHECK(dialog_it != update_scheduled_message_ids_.end());
auto erased_count = dialog_it->second.erase(message_id.get_scheduled_server_message_id());
CHECK(erased_count > 0);
if (dialog_it->second.empty()) {
update_scheduled_message_ids_.erase(dialog_it);
}
} else {
CHECK(message_id.is_server());
auto erased_count = update_message_ids_.erase(FullMessageId(dialog_id, message_id));
CHECK(erased_count > 0);
}
}
FullMessageId MessagesManager::on_get_message(tl_object_ptr<telegram_api::Message> message_ptr, bool from_update,
bool is_channel_message, bool is_scheduled, bool have_previous,
bool have_next, const char *source) {
return on_get_message(parse_telegram_api_message(std::move(message_ptr), is_scheduled, source), from_update,
is_channel_message, have_previous, have_next, source);
}
FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool from_update, bool is_channel_message,
bool have_previous, bool have_next, const char *source) {
DialogId dialog_id;
unique_ptr<Message> new_message;
std::tie(dialog_id, new_message) = create_message(std::move(message_info), is_channel_message);
if (new_message == nullptr) {
return FullMessageId();
}
MessageId message_id = new_message->message_id;
new_message->have_previous = have_previous;
new_message->have_next = have_next;
bool need_update = from_update;
bool need_update_dialog_pos = false;
MessageId old_message_id = find_old_message_id(dialog_id, message_id);
bool is_sent_message = false;
if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) {
LOG(INFO) << "Found temporary " << old_message_id << " for " << FullMessageId{dialog_id, message_id};
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!from_update && !message_id.is_scheduled()) {
if (message_id <= d->last_new_message_id) {
if (get_message_force(d, message_id, "receive missed unsent message not from update") != nullptr) {
LOG(ERROR) << "New " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source
<< " has identifier less than last_new_message_id = " << d->last_new_message_id;
return FullMessageId();
}
// if there is no message yet, then it is likely was missed because of a server bug and is being repaired via
// get_message_from_server from after_get_difference
if (!has_qts_messages(dialog_id)) {
// TODO move to INFO
LOG(ERROR) << "Receive " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source
<< " with identifier less than last_new_message_id = " << d->last_new_message_id
<< " and trying to add it anyway";
}
} else {
// TODO move to INFO
LOG(ERROR) << "Ignore " << old_message_id << "/" << message_id << " received not through update from " << source
<< ": " << oneline(to_string(get_message_object(dialog_id, new_message.get(), "on_get_message")));
if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) {
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
return FullMessageId();
}
}
delete_update_message_id(dialog_id, message_id);
if (!new_message->is_outgoing && dialog_id != get_my_dialog_id()) {
// sent message is not from me
LOG(ERROR) << "Sent in " << dialog_id << " " << message_id << " is sent by " << new_message->sender_user_id << "/"
<< new_message->sender_dialog_id;
return FullMessageId();
}
// must be called before delete_message
update_reply_to_message_id(dialog_id, old_message_id, message_id, true, "on_get_message");
being_readded_message_id_ = {dialog_id, old_message_id};
unique_ptr<Message> old_message =
delete_message(d, old_message_id, false, &need_update_dialog_pos, "add sent message");
if (old_message == nullptr) {
delete_sent_message_on_server(dialog_id, message_id, old_message_id);
being_readded_message_id_ = FullMessageId();
return FullMessageId();
}
old_message_id = old_message->message_id;
need_update = false;
if (old_message_id.is_valid() && message_id.is_valid() && message_id < old_message_id &&
!has_qts_messages(dialog_id) && !d->had_yet_unsent_message_id_overflow) {
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;
new_message->have_next = false;
update_message(d, old_message.get(), std::move(new_message), &need_update_dialog_pos, false);
new_message = std::move(old_message);
if (new_message->reply_to_message_id != MessageId() && new_message->reply_to_message_id.is_yet_unsent()) {
LOG(INFO) << "Drop reply to " << new_message->reply_to_message_id;
new_message->reply_to_message_id = MessageId();
}
set_message_id(new_message, message_id);
send_update_message_send_succeeded(d, old_message_id, new_message.get());
if (!message_id.is_scheduled()) {
is_sent_message = true;
}
if (!from_update) {
new_message->have_previous = have_previous;
new_message->have_next = have_next;
} else {
new_message->have_previous = true;
new_message->have_next = true;
}
}
const Message *m = add_message_to_dialog(dialog_id, std::move(new_message), from_update, &need_update,
&need_update_dialog_pos, source);
being_readded_message_id_ = FullMessageId();
Dialog *d = get_dialog(dialog_id);
if (m == nullptr) {
if (need_update_dialog_pos && d != nullptr) {
send_update_chat_last_message(d, "on_get_message");
}
if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) {
CHECK(d != nullptr);
if (!old_message_id.is_valid() || !message_id.is_valid() || old_message_id <= message_id) {
LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << message_id
<< " from " << source << ": " << debug_add_message_to_dialog_fail_reason_;
}
send_update_delete_messages(dialog_id, {message_id.get()}, true);
}
return FullMessageId();
}
CHECK(d != nullptr);
auto pcc_it = pending_created_dialogs_.find(dialog_id);
if (from_update && pcc_it != pending_created_dialogs_.end()) {
pcc_it->second.set_value(Unit());
pending_created_dialogs_.erase(pcc_it);
}
if (need_update) {
send_update_new_message(d, m);
}
if (is_sent_message) {
try_add_active_live_location(dialog_id, m);
// add_message_to_dialog will not update counts, because need_update == false
update_message_count_by_index(d, +1, m);
}
if (is_sent_message || (need_update && !message_id.is_scheduled())) {
update_reply_count_by_message(d, +1, m);
update_forward_count(dialog_id, m);
}
if (dialog_id.get_type() == DialogType::Channel && !have_input_peer(dialog_id, AccessRights::Read)) {
auto p = delete_message(d, message_id, false, &need_update_dialog_pos, "get a message in inaccessible chat");
CHECK(p.get() == m);
// CHECK(d->messages == nullptr);
send_update_delete_messages(dialog_id, {p->message_id.get()}, false);
// don't need to update dialog pos
return FullMessageId();
}
if (m->message_id.is_scheduled()) {
send_update_chat_has_scheduled_messages(d, false);
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "on_get_message");
}
// set dialog reply markup only after updateNewMessage and updateChatLastMessage are sent
if (need_update && m->reply_markup != nullptr && !m->message_id.is_scheduled() &&
m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard && m->reply_markup->is_personal &&
!td_->auth_manager_->is_bot()) {
set_dialog_reply_markup(d, message_id);
}
return FullMessageId(dialog_id, message_id);
}
void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source,
const Message *m) {
CHECK(!last_message_id.is_scheduled());
LOG(INFO) << "Set " << d->dialog_id << " last message to " << last_message_id << " from " << source;
d->last_message_id = last_message_id;
if (m != nullptr) {
d->last_media_album_id = m->media_album_id;
} else if (!last_message_id.is_valid()) {
d->last_media_album_id = 0;
} else {
m = get_message(d, last_message_id);
if (m == nullptr) {
LOG(ERROR) << "Failed to find last " << last_message_id << " in " << d->dialog_id;
d->last_media_album_id = 0;
} else {
d->last_media_album_id = m->media_album_id;
}
}
if (!last_message_id.is_valid()) {
d->suffix_load_first_message_id_ = MessageId();
d->suffix_load_done_ = false;
}
if (last_message_id.is_valid() && d->delete_last_message_date != 0) {
d->delete_last_message_date = 0;
d->deleted_last_message_id = MessageId();
d->is_last_message_deleted_locally = false;
on_dialog_updated(d->dialog_id, "update_delete_last_message_date");
}
if (d->pending_last_message_date != 0) {
d->pending_last_message_date = 0;
d->pending_last_message_id = MessageId();
}
}
void MessagesManager::set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id,
const char *source) {
CHECK(!first_database_message_id.is_scheduled());
if (first_database_message_id == d->first_database_message_id) {
return;
}
LOG(INFO) << "Set " << d->dialog_id << " first database message to " << first_database_message_id << " from "
<< source;
d->first_database_message_id = first_database_message_id;
on_dialog_updated(d->dialog_id, "set_dialog_first_database_message_id");
}
void MessagesManager::set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id,
const char *source, bool is_loaded_from_database) {
CHECK(!last_database_message_id.is_scheduled());
if (last_database_message_id == d->last_database_message_id) {
return;
}
LOG(INFO) << "Set " << d->dialog_id << " last database message to " << last_database_message_id << " from " << source;
d->debug_set_dialog_last_database_message_id = source;
d->last_database_message_id = last_database_message_id;
if (!is_loaded_from_database) {
on_dialog_updated(d->dialog_id, "set_dialog_last_database_message_id");
}
}
void MessagesManager::remove_dialog_newer_messages(Dialog *d, MessageId from_message_id, const char *source) {
LOG(INFO) << "Remove messages in " << d->dialog_id << " newer than " << from_message_id << " from " << source;
CHECK(!d->last_new_message_id.is_valid());
delete_all_dialog_messages_from_database(d, MessageId::max(), "remove_dialog_newer_messages");
set_dialog_first_database_message_id(d, MessageId(), "remove_dialog_newer_messages");
set_dialog_last_database_message_id(d, MessageId(), source);
if (d->dialog_id.get_type() != DialogType::SecretChat && !d->is_empty) {
d->have_full_history = false;
d->have_full_history_source = 0;
}
invalidate_message_indexes(d);
vector<MessageId> to_delete_message_ids;
find_newer_messages(d->messages.get(), from_message_id, to_delete_message_ids);
td::remove_if(to_delete_message_ids, [](MessageId message_id) { return message_id.is_yet_unsent(); });
if (!to_delete_message_ids.empty()) {
LOG(INFO) << "Delete " << format::as_array(to_delete_message_ids) << " newer than " << from_message_id << " in "
<< d->dialog_id << " from " << source;
vector<int64> deleted_message_ids;
bool need_update_dialog_pos = false;
for (auto message_id : to_delete_message_ids) {
auto message = delete_message(d, message_id, false, &need_update_dialog_pos, "remove_dialog_newer_messages");
if (message != nullptr) {
deleted_message_ids.push_back(message->message_id.get());
}
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "remove_dialog_newer_messages");
}
send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), false);
}
}
void MessagesManager::set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source) {
CHECK(!last_new_message_id.is_scheduled());
LOG_CHECK(last_new_message_id > d->last_new_message_id)
<< last_new_message_id << " " << d->last_new_message_id << " " << source;
CHECK(d->dialog_id.get_type() == DialogType::SecretChat || last_new_message_id.is_server());
if (!d->last_new_message_id.is_valid()) {
remove_dialog_newer_messages(d, last_new_message_id, source);
auto last_new_message = get_message(d, last_new_message_id);
if (last_new_message != nullptr) {
add_message_to_database(d, last_new_message, source);
set_dialog_first_database_message_id(d, last_new_message_id, source);
set_dialog_last_database_message_id(d, last_new_message_id, source);
try_restore_dialog_reply_markup(d, last_new_message);
}
}
LOG(INFO) << "Set " << d->dialog_id << " last new message to " << last_new_message_id << " from " << source;
d->last_new_message_id = last_new_message_id;
on_dialog_updated(d->dialog_id, source);
}
void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date, MessageId last_clear_history_message_id,
const char *source, bool is_loaded_from_database) {
CHECK(!last_clear_history_message_id.is_scheduled());
if (d->last_clear_history_message_id == last_clear_history_message_id && d->last_clear_history_date == date) {
return;
}
LOG(INFO) << "Set " << d->dialog_id << " last clear history date to " << date << " of "
<< last_clear_history_message_id << " from " << source;
if (d->last_clear_history_message_id.is_valid()) {
switch (d->dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
last_clear_history_message_id_to_dialog_id_.erase(d->last_clear_history_message_id);
break;
case DialogType::Channel:
case DialogType::SecretChat:
// nothing to do
break;
case DialogType::None:
default:
UNREACHABLE();
}
}
d->last_clear_history_date = date;
d->last_clear_history_message_id = last_clear_history_message_id;
if (!is_loaded_from_database) {
on_dialog_updated(d->dialog_id, "set_dialog_last_clear_history_date");
}
if (d->last_clear_history_message_id.is_valid()) {
switch (d->dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
last_clear_history_message_id_to_dialog_id_[d->last_clear_history_message_id] = d->dialog_id;
break;
case DialogType::Channel:
case DialogType::SecretChat:
// nothing to do
break;
case DialogType::None:
default:
UNREACHABLE();
}
}
}
void MessagesManager::set_dialog_unread_mention_count(Dialog *d, int32 unread_mention_count) {
CHECK(d->unread_mention_count != unread_mention_count);
CHECK(unread_mention_count >= 0);
d->unread_mention_count = unread_mention_count;
d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)] = unread_mention_count;
}
void MessagesManager::set_dialog_unread_reaction_count(Dialog *d, int32 unread_reaction_count) {
CHECK(d->unread_reaction_count != unread_reaction_count);
CHECK(unread_reaction_count >= 0);
d->unread_reaction_count = unread_reaction_count;
d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)] = unread_reaction_count;
}
void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) {
LOG(INFO) << "Set " << d->dialog_id << " is_empty to true from " << source;
CHECK(d->have_full_history);
d->is_empty = true;
if (d->server_unread_count + d->local_unread_count > 0) {
MessageId max_message_id =
d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id;
if (max_message_id.is_valid()) {
read_history_inbox(d->dialog_id, max_message_id, -1, "set_dialog_is_empty");
}
if (d->server_unread_count != 0 || d->local_unread_count != 0) {
set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "set_dialog_is_empty");
}
}
if (d->unread_mention_count > 0) {
set_dialog_unread_mention_count(d, 0);
send_update_chat_unread_mention_count(d);
}
if (d->unread_reaction_count > 0) {
set_dialog_unread_reaction_count(d, 0);
send_update_chat_unread_reaction_count(d, "set_dialog_is_empty");
}
if (d->reply_markup_message_id != MessageId()) {
set_dialog_reply_markup(d, MessageId());
}
std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0);
d->notification_id_to_message_id.clear();
if (d->delete_last_message_date != 0) {
if (d->is_last_message_deleted_locally && d->last_clear_history_date == 0) {
set_dialog_last_clear_history_date(d, d->delete_last_message_date, d->deleted_last_message_id,
"set_dialog_is_empty");
}
d->delete_last_message_date = 0;
d->deleted_last_message_id = MessageId();
d->is_last_message_deleted_locally = false;
on_dialog_updated(d->dialog_id, "set_dialog_is_empty");
}
if (d->pending_last_message_date != 0) {
d->pending_last_message_date = 0;
d->pending_last_message_id = MessageId();
}
if (d->last_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, MessageId(), "set_dialog_is_empty");
set_dialog_last_database_message_id(d, MessageId(), "set_dialog_is_empty");
}
update_dialog_pos(d, source);
}
bool MessagesManager::is_dialog_pinned(DialogListId dialog_list_id, DialogId dialog_id) const {
if (get_dialog_pinned_order(dialog_list_id, dialog_id) != DEFAULT_ORDER) {
return true;
}
if (dialog_list_id.is_filter()) {
const auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
if (filter != nullptr && InputDialogId::contains(filter->pinned_dialog_ids, dialog_id)) {
return true;
}
}
return false;
}
int64 MessagesManager::get_dialog_pinned_order(DialogListId dialog_list_id, DialogId dialog_id) const {
return get_dialog_pinned_order(get_dialog_list(dialog_list_id), dialog_id);
}
int64 MessagesManager::get_dialog_pinned_order(const DialogList *list, DialogId dialog_id) {
if (list != nullptr && !list->pinned_dialog_id_orders_.empty()) {
auto it = list->pinned_dialog_id_orders_.find(dialog_id);
if (it != list->pinned_dialog_id_orders_.end()) {
return it->second;
}
}
return DEFAULT_ORDER;
}
bool MessagesManager::set_dialog_is_pinned(DialogId dialog_id, bool is_pinned) {
if (td_->auth_manager_->is_bot()) {
return false;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
return set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
}
// only removes the Dialog from the dialog list, but changes nothing in the corresponding DialogFilter
bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog *d, bool is_pinned,
bool need_update_dialog_lists) {
if (td_->auth_manager_->is_bot()) {
return false;
}
CHECK(d != nullptr);
if (d->order == DEFAULT_ORDER && is_pinned) {
// the chat can't be pinned
return false;
}
auto positions = get_dialog_positions(d);
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr) {
return false;
}
if (!list->are_pinned_dialogs_inited_) {
return false;
}
bool was_pinned = false;
for (size_t pos = 0; pos < list->pinned_dialogs_.size(); pos++) {
auto &pinned_dialog = list->pinned_dialogs_[pos];
if (pinned_dialog.get_dialog_id() == d->dialog_id) {
// the dialog was already pinned
if (is_pinned) {
if (pos == 0) {
return false;
}
auto order = get_next_pinned_dialog_order();
pinned_dialog = DialogDate(order, d->dialog_id);
std::rotate(list->pinned_dialogs_.begin(), list->pinned_dialogs_.begin() + pos,
list->pinned_dialogs_.begin() + pos + 1);
list->pinned_dialog_id_orders_[d->dialog_id] = order;
} else {
list->pinned_dialogs_.erase(list->pinned_dialogs_.begin() + pos);
list->pinned_dialog_id_orders_.erase(d->dialog_id);
}
was_pinned = true;
break;
}
}
if (!was_pinned) {
if (!is_pinned) {
return false;
}
auto order = get_next_pinned_dialog_order();
list->pinned_dialogs_.insert(list->pinned_dialogs_.begin(), {order, d->dialog_id});
list->pinned_dialog_id_orders_.emplace(d->dialog_id, order);
}
LOG(INFO) << "Set " << d->dialog_id << " is pinned in " << dialog_list_id << " to " << is_pinned;
save_pinned_folder_dialog_ids(*list);
if (need_update_dialog_lists) {
update_dialog_lists(d, std::move(positions), true, false, "set_dialog_is_pinned");
}
return true;
}
void MessagesManager::save_pinned_folder_dialog_ids(const DialogList &list) const {
if (!list.dialog_list_id.is_folder() || !G()->parameters().use_message_db) {
return;
}
G()->td_db()->get_binlog_pmc()->set(
PSTRING() << "pinned_dialog_ids" << list.dialog_list_id.get_folder_id().get(),
implode(transform(list.pinned_dialogs_,
[](auto &pinned_dialog) { return PSTRING() << pinned_dialog.get_dialog_id().get(); }),
','));
}
void MessagesManager::set_dialog_reply_markup(Dialog *d, MessageId message_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(!message_id.is_scheduled());
if (d->reply_markup_message_id != message_id) {
on_dialog_updated(d->dialog_id, "set_dialog_reply_markup");
}
d->need_restore_reply_markup = false;
if (d->reply_markup_message_id.is_valid() || message_id.is_valid()) {
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_reply_markup";
d->reply_markup_message_id = message_id;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatReplyMarkup>(d->dialog_id.get(), message_id.get()));
}
}
void MessagesManager::try_restore_dialog_reply_markup(Dialog *d, const Message *m) {
if (!d->need_restore_reply_markup || td_->auth_manager_->is_bot()) {
return;
}
CHECK(!m->message_id.is_scheduled());
if (m->had_reply_markup) {
LOG(INFO) << "Restore deleted reply markup in " << d->dialog_id;
set_dialog_reply_markup(d, MessageId());
} else if (m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard &&
m->reply_markup->is_personal) {
LOG(INFO) << "Restore reply markup in " << d->dialog_id << " to " << m->message_id;
set_dialog_reply_markup(d, m->message_id);
}
}
void MessagesManager::set_dialog_pinned_message_notification(Dialog *d, MessageId message_id, const char *source) {
CHECK(d != nullptr);
CHECK(!message_id.is_scheduled());
auto old_message_id = d->pinned_message_notification_message_id;
if (old_message_id == message_id) {
return;
}
VLOG(notifications) << "Change pinned message notification in " << d->dialog_id << " from " << old_message_id
<< " to " << message_id;
if (old_message_id.is_valid()) {
auto m = get_message_force(d, old_message_id, source);
if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) {
// Can't remove pinned_message_notification_message_id before the call,
// because the notification needs to be still active inside remove_message_notification_id
remove_message_notification_id(d, m, true, false, true);
on_message_changed(d, m, false, source);
} else {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
d->mention_notification_group.group_id, old_message_id, false, source);
}
}
d->pinned_message_notification_message_id = message_id;
on_dialog_updated(d->dialog_id, source);
}
void MessagesManager::remove_dialog_pinned_message_notification(Dialog *d, const char *source) {
set_dialog_pinned_message_notification(d, MessageId(), source);
}
void MessagesManager::remove_scope_pinned_message_notifications(NotificationSettingsScope scope) {
VLOG(notifications) << "Remove pinned message notifications in " << scope;
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (d->notification_settings.use_default_disable_pinned_message_notifications &&
d->mention_notification_group.group_id.is_valid() && d->pinned_message_notification_message_id.is_valid() &&
get_dialog_notification_setting_scope(dialog_id) == scope) {
remove_dialog_pinned_message_notification(d, "remove_scope_pinned_message_notifications");
}
});
}
void MessagesManager::on_update_scope_mention_notifications(NotificationSettingsScope scope,
bool disable_mention_notifications) {
VLOG(notifications) << "Remove mention notifications in " << scope;
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (d->notification_settings.use_default_disable_mention_notifications &&
get_dialog_notification_setting_scope(dialog_id) == scope) {
if (!disable_mention_notifications) {
update_dialog_mention_notification_count(d);
} else {
remove_dialog_mention_notifications(d);
}
}
});
}
void MessagesManager::remove_dialog_mention_notifications(Dialog *d) {
auto notification_group_id = d->mention_notification_group.group_id;
if (!notification_group_id.is_valid()) {
return;
}
if (d->unread_mention_count == 0) {
return;
}
CHECK(!d->being_added_message_id.is_valid());
VLOG(notifications) << "Remove mention notifications in " << d->dialog_id;
vector<MessageId> message_ids;
FlatHashSet<NotificationId, NotificationIdHash> removed_notification_ids_set;
find_messages(d->messages.get(), message_ids, [](const Message *m) { return m->contains_unread_mention; });
VLOG(notifications) << "Found unread mentions in " << message_ids;
for (auto &message_id : message_ids) {
auto m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
if (m->notification_id.is_valid() && is_message_notification_active(d, m) &&
is_from_mention_notification_group(m)) {
removed_notification_ids_set.insert(m->notification_id);
}
}
message_ids = td_->notification_manager_->get_notification_group_message_ids(notification_group_id);
VLOG(notifications) << "Found active mention notifications in " << message_ids;
for (auto &message_id : message_ids) {
CHECK(!message_id.is_scheduled());
if (message_id != d->pinned_message_notification_message_id) {
auto m = get_message_force(d, message_id, "remove_dialog_mention_notifications");
if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) {
CHECK(is_from_mention_notification_group(m));
removed_notification_ids_set.insert(m->notification_id);
}
}
}
vector<NotificationId> removed_notification_ids;
for (auto notification_id : removed_notification_ids_set) {
removed_notification_ids.push_back(notification_id);
}
for (size_t i = 0; i < removed_notification_ids.size(); i++) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, notification_group_id,
removed_notification_ids[i], false, i + 1 == removed_notification_ids.size(), Promise<Unit>(),
"remove_dialog_mention_notifications");
}
}
bool MessagesManager::set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info,
int32 last_notification_date, NotificationId last_notification_id,
const char *source) {
if (group_info.last_notification_date != last_notification_date ||
group_info.last_notification_id != last_notification_id) {
VLOG(notifications) << "Set " << group_info.group_id << '/' << dialog_id << " last notification to "
<< last_notification_id << " sent at " << last_notification_date << " from " << source;
group_info.last_notification_date = last_notification_date;
group_info.last_notification_id = last_notification_id;
group_info.is_changed = true;
on_dialog_updated(dialog_id, "set_dialog_last_notification");
return true;
}
return false;
}
void MessagesManager::on_update_sent_text_message(int64 random_id,
tl_object_ptr<telegram_api::MessageMedia> message_media,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities) {
int32 message_media_id = message_media == nullptr ? telegram_api::messageMediaEmpty::ID : message_media->get_id();
LOG_IF(ERROR, message_media_id != telegram_api::messageMediaWebPage::ID &&
message_media_id != telegram_api::messageMediaEmpty::ID)
<< "Receive non web-page media for text message: " << oneline(to_string(message_media));
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// result of sending message has already been received through getDifference
return;
}
auto full_message_id = it->second;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
auto m = get_message_force(d, full_message_id.get_message_id(), "on_update_sent_text_message");
if (m == nullptr) {
// message has already been deleted
return;
}
CHECK(m->message_id.is_yet_unsent());
full_message_id = FullMessageId(dialog_id, m->message_id);
if (m->content->get_type() != MessageContentType::Text) {
LOG(ERROR) << "Text message content has been already changed to " << m->content->get_type();
return;
}
const FormattedText *old_message_text = get_message_content_text(m->content.get());
CHECK(old_message_text != nullptr);
FormattedText new_message_text = get_message_text(
td_->contacts_manager_.get(), old_message_text->text, std::move(entities), true, td_->auth_manager_->is_bot(),
m->forward_info ? m->forward_info->date : m->date, m->media_album_id != 0, "on_update_sent_text_message");
auto new_content = get_message_content(td_, std::move(new_message_text), std::move(message_media), dialog_id,
true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/,
nullptr, "on_update_sent_text_message");
if (new_content->get_type() != MessageContentType::Text) {
LOG(ERROR) << "Text message content has changed to " << new_content->get_type();
return;
}
bool need_update = false;
bool is_content_changed = false;
merge_message_contents(td_, m->content.get(), new_content.get(), need_message_changed_warning(m), dialog_id, false,
is_content_changed, need_update);
if (is_content_changed || need_update) {
reregister_message_content(td_, m->content.get(), new_content.get(), full_message_id,
"on_update_sent_text_message");
m->content = std::move(new_content);
m->is_content_secret = is_secret_message_content(m->ttl, MessageContentType::Text);
if (need_update) {
send_update_message_content(d, m, true, "on_update_sent_text_message");
}
on_message_changed(d, m, need_update, "on_update_sent_text_message");
}
}
void MessagesManager::delete_pending_message_web_page(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
Message *m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
MessageContent *content = m->content.get();
CHECK(has_message_content_web_page(content));
unregister_message_content(td_, content, full_message_id, "delete_pending_message_web_page");
remove_message_content_web_page(content);
register_message_content(td_, content, full_message_id, "delete_pending_message_web_page");
// don't need to send an updateMessageContent, because the web page was pending
on_message_changed(d, m, false, "delete_pending_message_web_page");
}
void MessagesManager::on_get_dialogs(FolderId folder_id, vector<tl_object_ptr<telegram_api::Dialog>> &&dialog_folders,
int32 total_count, vector<tl_object_ptr<telegram_api::Message>> &&messages,
Promise<Unit> &&promise) {
if (td_->updates_manager_->running_get_difference()) {
LOG(INFO) << "Postpone result of getDialogs";
pending_on_get_dialogs_.push_back(PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count,
std::move(messages), std::move(promise)});
return;
}
bool from_dialog_list = total_count >= 0;
bool from_get_dialog = total_count == -1;
bool from_pinned_dialog_list = total_count == -2;
if (from_get_dialog && dialog_folders.size() == 1 && dialog_folders[0]->get_id() == telegram_api::dialog::ID) {
DialogId dialog_id(static_cast<const telegram_api::dialog *>(dialog_folders[0].get())->peer_);
if (dialog_id.is_valid() && running_get_channel_difference(dialog_id)) {
LOG(INFO) << "Postpone result of channels getDialogs for " << dialog_id;
pending_channel_on_get_dialogs_.emplace(
dialog_id, PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count, std::move(messages),
std::move(promise)});
return;
}
}
vector<tl_object_ptr<telegram_api::dialog>> dialogs;
for (auto &dialog_folder : dialog_folders) {
switch (dialog_folder->get_id()) {
case telegram_api::dialog::ID:
dialogs.push_back(telegram_api::move_object_as<telegram_api::dialog>(dialog_folder));
break;
case telegram_api::dialogFolder::ID: {
auto folder = telegram_api::move_object_as<telegram_api::dialogFolder>(dialog_folder);
if (from_pinned_dialog_list) {
// TODO update unread_muted_peers_count:int unread_unmuted_peers_count:int
// unread_muted_messages_count:int unread_unmuted_messages_count:int
FolderId folder_folder_id(folder->folder_->id_);
if (folder_folder_id == FolderId::archive()) {
// archive is expected in pinned dialogs list
break;
}
}
LOG(ERROR) << "Receive unexpected " << to_string(folder);
break;
}
default:
UNREACHABLE();
}
}
const char *source = nullptr;
if (from_get_dialog) {
LOG(INFO) << "Process " << dialogs.size() << " chats";
source = "get chat";
} else if (from_pinned_dialog_list) {
LOG(INFO) << "Process " << dialogs.size() << " pinned chats in " << folder_id;
source = "get pinned chats";
} else {
LOG(INFO) << "Process " << dialogs.size() << " chats out of " << total_count << " in " << folder_id;
source = "get chat list";
}
FlatHashMap<FullMessageId, DialogDate, FullMessageIdHash> full_message_id_to_dialog_date;
FlatHashMap<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
for (auto &message : messages) {
auto full_message_id = FullMessageId::get_full_message_id(message, false);
if (!full_message_id.get_message_id().is_valid()) {
continue;
}
if (from_dialog_list) {
auto message_date = get_message_date(message);
int64 order = get_dialog_order(full_message_id.get_message_id(), message_date);
full_message_id_to_dialog_date.emplace(full_message_id, DialogDate(order, full_message_id.get_dialog_id()));
}
full_message_id_to_message[full_message_id] = std::move(message);
}
DialogDate max_dialog_date = MIN_DIALOG_DATE;
for (auto &dialog : dialogs) {
// LOG(INFO) << to_string(dialog);
DialogId dialog_id(dialog->peer_);
bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0;
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive wrong " << dialog_id;
return promise.set_error(Status::Error(500, "Wrong query result returned: receive wrong chat identifier"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
if (has_pts) {
LOG(ERROR) << "Receive user or group " << dialog_id << " with pts";
return promise.set_error(
Status::Error(500, "Wrong query result returned: receive user or basic group chat with pts"));
}
break;
case DialogType::Channel:
if (!has_pts) {
LOG(ERROR) << "Receive channel " << dialog_id << " without pts";
return promise.set_error(
Status::Error(500, "Wrong query result returned: receive supergroup chat without pts"));
}
break;
case DialogType::SecretChat:
case DialogType::None:
default:
UNREACHABLE();
return promise.set_error(Status::Error(500, "UNREACHABLE"));
}
if (from_dialog_list) {
MessageId last_message_id(ServerMessageId(dialog->top_message_));
if (last_message_id.is_valid()) {
FullMessageId full_message_id(dialog_id, last_message_id);
auto it = full_message_id_to_dialog_date.find(full_message_id);
if (it == full_message_id_to_dialog_date.end()) {
LOG(ERROR) << "Last " << last_message_id << " in " << dialog_id << " not found";
return promise.set_error(Status::Error(500, "Wrong query result returned: last message not found"));
}
FolderId dialog_folder_id(dialog->folder_id_);
if (dialog_folder_id != folder_id) {
LOG(ERROR) << "Receive " << dialog_id << " in " << dialog_folder_id << " instead of " << folder_id;
continue;
}
DialogDate dialog_date = it->second;
CHECK(dialog_date.get_dialog_id() == dialog_id);
if (dialog_date.get_date() > 0 && max_dialog_date < dialog_date) {
max_dialog_date = dialog_date;
}
} else {
LOG(ERROR) << "Receive " << last_message_id << " as last chat message";
continue;
}
}
}
if (from_dialog_list && total_count < narrow_cast<int32>(dialogs.size())) {
LOG(ERROR) << "Receive chat total_count = " << total_count << ", but " << dialogs.size() << " chats";
total_count = narrow_cast<int32>(dialogs.size());
}
vector<DialogId> added_dialog_ids;
for (auto &dialog : dialogs) {
MessageId last_message_id(ServerMessageId(dialog->top_message_));
if (!last_message_id.is_valid() && from_dialog_list) {
// skip dialogs without messages
total_count--;
continue;
}
DialogId dialog_id(dialog->peer_);
if (td::contains(added_dialog_ids, dialog_id)) {
LOG(ERROR) << "Receive " << dialog_id << " twice in result of getChats with total_count = " << total_count;
continue;
}
added_dialog_ids.push_back(dialog_id);
Dialog *d = get_dialog_force(dialog_id, source);
bool need_update_dialog_pos = false;
CHECK(!being_added_dialog_id_.is_valid());
being_added_dialog_id_ = dialog_id;
if (d == nullptr) {
d = add_dialog(dialog_id, source);
need_update_dialog_pos = true;
} else {
LOG(INFO) << "Receive already created " << dialog_id;
CHECK(d->dialog_id == dialog_id);
}
bool is_new = d->last_new_message_id == MessageId();
auto positions = get_dialog_positions(d);
set_dialog_folder_id(d, FolderId(dialog->folder_id_));
on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), source);
if (!d->notification_settings.is_synchronized && !td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Failed to synchronize settings in " << dialog_id;
d->notification_settings.is_synchronized = true;
on_dialog_updated(dialog_id, "set notification_settings.is_synchronized");
}
if (dialog->unread_count_ < 0) {
LOG(ERROR) << "Receive " << dialog->unread_count_ << " as number of unread messages in " << dialog_id;
dialog->unread_count_ = 0;
}
MessageId read_inbox_max_message_id = MessageId(ServerMessageId(dialog->read_inbox_max_id_));
if (!read_inbox_max_message_id.is_valid() && read_inbox_max_message_id != MessageId()) {
LOG(ERROR) << "Receive " << read_inbox_max_message_id << " as last read inbox message in " << dialog_id;
read_inbox_max_message_id = MessageId();
}
MessageId read_outbox_max_message_id = MessageId(ServerMessageId(dialog->read_outbox_max_id_));
if (!read_outbox_max_message_id.is_valid() && read_outbox_max_message_id != MessageId()) {
LOG(ERROR) << "Receive " << read_outbox_max_message_id << " as last read outbox message in " << dialog_id;
read_outbox_max_message_id = MessageId();
}
if (dialog->unread_mentions_count_ < 0) {
LOG(ERROR) << "Receive " << dialog->unread_mentions_count_ << " as number of unread mention messages in "
<< dialog_id;
dialog->unread_mentions_count_ = 0;
}
if (dialog->unread_reactions_count_ < 0) {
LOG(ERROR) << "Receive " << dialog->unread_reactions_count_ << " as number of messages with unread reactions in "
<< dialog_id;
dialog->unread_reactions_count_ = 0;
}
if (dialog->ttl_period_ < 0) {
LOG(ERROR) << "Receive " << dialog->ttl_period_ << " as message TTL in " << dialog_id;
dialog->ttl_period_ = 0;
}
if (!d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get is_blocked from the server
// TODO add is_blocked to telegram_api::dialog
reload_dialog_info_full(dialog_id, "on_get_dialogs init is_blocked");
} else 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
reload_dialog_info_full(dialog_id, "on_get_dialogs init has_bots");
} else if (!d->is_theme_name_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get theme_name from the server
// TODO add theme_name to telegram_api::dialog
reload_dialog_info_full(dialog_id, "on_get_dialogs init theme_name");
} else 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());
} else if (!d->is_available_reactions_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get dialog available reactions from the server
reload_dialog_info_full(dialog_id, "on_get_dialogs init available_reactions");
}
need_update_dialog_pos |= update_dialog_draft_message(
d, get_draft_message(td_->contacts_manager_.get(), std::move(dialog->draft_)), true, false);
if (is_new) {
bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0;
if (last_message_id.is_valid()) {
FullMessageId full_message_id(dialog_id, last_message_id);
auto it = full_message_id_to_message.find(full_message_id);
if (it == full_message_id_to_message.end()) {
LOG(ERROR) << "Last " << full_message_id << " not found";
} else if (!has_pts || d->pts == 0 || dialog->pts_ <= d->pts || d->is_channel_difference_finished) {
auto last_message = std::move(it->second);
auto added_full_message_id =
on_get_message(std::move(last_message), false, has_pts, false, false, false, source);
CHECK(d->last_new_message_id == MessageId());
set_dialog_last_new_message_id(d, last_message_id, source);
if (d->last_new_message_id > d->last_message_id && added_full_message_id.get_message_id().is_valid()) {
CHECK(added_full_message_id.get_message_id() == d->last_new_message_id);
set_dialog_last_message_id(d, d->last_new_message_id, source);
send_update_chat_last_message(d, source);
}
} else if (dialog_id.get_type() == DialogType::Channel) {
get_channel_difference(dialog_id, d->pts, true, source);
}
}
if (has_pts && !running_get_channel_difference(dialog_id)) {
set_channel_pts(d, dialog->pts_, source);
}
}
bool is_marked_as_unread = dialog->unread_mark_;
if (is_marked_as_unread != d->is_marked_as_unread) {
set_dialog_is_marked_as_unread(d, is_marked_as_unread);
}
if (need_update_dialog_pos) {
update_dialog_pos(d, source);
}
if (!td_->auth_manager_->is_bot() && !from_pinned_dialog_list) {
// set is_pinned only after updating dialog pos to ensure that order is initialized
bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0;
bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id);
if (is_pinned != was_pinned) {
set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
}
}
if (!G()->parameters().use_message_db || is_new || !d->is_last_read_inbox_message_id_inited ||
d->need_repair_server_unread_count) {
if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() &&
read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) {
read_inbox_max_message_id = d->last_read_inbox_message_id;
}
if (d->need_repair_server_unread_count &&
(d->last_read_inbox_message_id <= read_inbox_max_message_id || !need_unread_counter(d->order) ||
!have_input_peer(dialog_id, AccessRights::Read))) {
LOG(INFO) << "Repaired server unread count in " << dialog_id << " from " << d->last_read_inbox_message_id << "/"
<< d->server_unread_count << " to " << read_inbox_max_message_id << "/" << dialog->unread_count_;
d->need_repair_server_unread_count = false;
on_dialog_updated(dialog_id, "repaired dialog server unread count");
}
if (d->need_repair_server_unread_count) {
auto &previous_message_id = previous_repaired_read_inbox_max_message_id_[dialog_id];
if (previous_message_id >= read_inbox_max_message_id) {
// protect from sending the request in a loop
if (d->server_unread_count != 0) {
LOG(ERROR) << "Failed to repair server unread count in " << dialog_id
<< ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id << " after "
<< previous_message_id << ", but messages are read up to " << d->last_read_inbox_message_id;
} else {
LOG(INFO) << "Failed to repair server unread count in " << dialog_id
<< ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id
<< ", but messages are read up to " << d->last_read_inbox_message_id
<< ". Likely all messages after " << read_inbox_max_message_id << " are outgoing";
}
d->need_repair_server_unread_count = false;
on_dialog_updated(dialog_id, "failed to repair dialog server unread count");
} else {
LOG(INFO) << "Have last_read_inbox_message_id = " << d->last_read_inbox_message_id << ", but received only "
<< read_inbox_max_message_id << " from the server, trying to repair server unread count again";
previous_message_id = read_inbox_max_message_id;
repair_server_unread_count(dialog_id, d->server_unread_count, source);
}
}
if (!d->need_repair_server_unread_count) {
previous_repaired_read_inbox_max_message_id_.erase(dialog_id);
}
if ((d->server_unread_count != dialog->unread_count_ &&
d->last_read_inbox_message_id == read_inbox_max_message_id) ||
d->last_read_inbox_message_id < read_inbox_max_message_id) {
set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, dialog->unread_count_,
d->local_unread_count, true, source);
}
if (!d->is_last_read_inbox_message_id_inited) {
d->is_last_read_inbox_message_id_inited = true;
on_dialog_updated(dialog_id, "set is_last_read_inbox_message_id_inited");
}
}
if (!G()->parameters().use_message_db || is_new || !d->is_last_read_outbox_message_id_inited) {
if (d->last_read_outbox_message_id < read_outbox_max_message_id) {
set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id);
}
if (!d->is_last_read_outbox_message_id_inited) {
d->is_last_read_outbox_message_id_inited = true;
on_dialog_updated(dialog_id, "set is_last_read_outbox_message_id_inited");
}
}
if (!G()->parameters().use_message_db || is_new) {
if (d->unread_mention_count != dialog->unread_mentions_count_) {
set_dialog_unread_mention_count(d, dialog->unread_mentions_count_);
update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
if (d->unread_reaction_count != dialog->unread_reactions_count_) {
set_dialog_unread_reaction_count(d, dialog->unread_reactions_count_);
// update_dialog_mention_notification_count(d);
send_update_chat_unread_reaction_count(d, source);
}
}
set_dialog_message_ttl(d, MessageTtl(dialog->ttl_period_));
being_added_dialog_id_ = DialogId();
update_dialog_lists(d, std::move(positions), true, false, source);
}
if (from_dialog_list) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(total_count >= 0);
auto &folder_list = add_dialog_list(DialogListId(folder_id));
if (folder_list.server_dialog_total_count_ != total_count) {
auto old_dialog_total_count = get_dialog_total_count(folder_list);
folder_list.server_dialog_total_count_ = total_count;
if (folder_list.is_dialog_unread_count_inited_) {
if (old_dialog_total_count != get_dialog_total_count(folder_list)) {
send_update_unread_chat_count(folder_list, DialogId(), true, source);
} else {
save_unread_chat_count(folder_list);
}
}
}
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
if (dialogs.empty()) {
// if there are no more dialogs on the server
max_dialog_date = MAX_DIALOG_DATE;
}
if (folder->last_server_dialog_date_ < max_dialog_date) {
folder->last_server_dialog_date_ = max_dialog_date;
update_last_dialog_date(folder_id);
} else if (promise) {
LOG(ERROR) << "Last server dialog date didn't increased from " << folder->last_server_dialog_date_ << " to "
<< max_dialog_date << " after receiving " << dialogs.size() << " chats " << added_dialog_ids
<< " from " << total_count << " in " << folder_id
<< ". last_dialog_date = " << folder->folder_last_dialog_date_
<< ", last_loaded_database_dialog_date = " << folder->last_loaded_database_dialog_date_;
}
}
if (from_pinned_dialog_list) {
CHECK(!td_->auth_manager_->is_bot());
auto *folder_list = get_dialog_list(DialogListId(folder_id));
CHECK(folder_list != nullptr);
auto pinned_dialog_ids = remove_secret_chat_dialog_ids(get_pinned_dialog_ids(DialogListId(folder_id)));
bool are_pinned_dialogs_saved = folder_list->are_pinned_dialogs_inited_;
folder_list->are_pinned_dialogs_inited_ = true;
if (pinned_dialog_ids != added_dialog_ids) {
LOG(INFO) << "Update pinned chats order from " << format::as_array(pinned_dialog_ids) << " to "
<< format::as_array(added_dialog_ids);
FlatHashSet<DialogId, DialogIdHash> old_pinned_dialog_ids;
for (auto pinned_dialog_id : pinned_dialog_ids) {
old_pinned_dialog_ids.insert(pinned_dialog_id);
}
std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
std::reverse(added_dialog_ids.begin(), added_dialog_ids.end());
auto old_it = pinned_dialog_ids.begin();
for (auto dialog_id : added_dialog_ids) {
old_pinned_dialog_ids.erase(dialog_id);
while (old_it < pinned_dialog_ids.end()) {
if (*old_it == dialog_id) {
break;
}
++old_it;
}
if (old_it < pinned_dialog_ids.end()) {
// leave dialog where it is
++old_it;
continue;
}
if (set_dialog_is_pinned(dialog_id, true)) {
are_pinned_dialogs_saved = true;
}
}
for (auto dialog_id : old_pinned_dialog_ids) {
Dialog *d = get_dialog_force(dialog_id, "on_get_dialogs pinned");
if (d == nullptr) {
LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << folder_id;
force_create_dialog(dialog_id, "from_pinned_dialog_list", true);
d = get_dialog_force(dialog_id, "on_get_dialogs pinned 2");
}
if (d != nullptr && set_dialog_is_pinned(DialogListId(folder_id), d, false)) {
are_pinned_dialogs_saved = true;
}
}
} else {
LOG(INFO) << "Pinned chats are not changed";
}
update_list_last_pinned_dialog_date(*folder_list);
if (!are_pinned_dialogs_saved && G()->parameters().use_message_db) {
LOG(INFO) << "Save empty pinned chat list in " << folder_id;
G()->td_db()->get_binlog_pmc()->set(PSTRING() << "pinned_dialog_ids" << folder_id.get(), "");
}
}
promise.set_value(Unit());
}
void MessagesManager::dump_debug_message_op(const Dialog *d, int priority) {
if (!is_debug_message_op_enabled()) {
return;
}
if (d == nullptr) {
LOG(ERROR) << "Chat not found";
return;
}
static int last_dumped_priority = -1;
if (priority <= last_dumped_priority) {
LOG(ERROR) << "Skip dump " << d->dialog_id;
return;
}
last_dumped_priority = priority;
for (auto &op : d->debug_message_op) {
switch (op.type) {
case Dialog::MessageOp::Add: {
LOG(ERROR) << "MessageOpAdd at " << op.date << " " << op.message_id << " " << op.content_type << " "
<< op.from_update << " " << op.have_previous << " " << op.have_next << " " << op.source;
break;
}
case Dialog::MessageOp::SetPts: {
LOG(ERROR) << "MessageOpSetPts at " << op.date << " " << op.pts << " " << op.source;
break;
}
case Dialog::MessageOp::Delete: {
LOG(ERROR) << "MessageOpDelete at " << op.date << " " << op.message_id << " " << op.content_type << " "
<< op.from_update << " " << op.have_previous << " " << op.have_next << " " << op.source;
break;
}
case Dialog::MessageOp::DeleteAll: {
LOG(ERROR) << "MessageOpDeleteAll at " << op.date << " " << op.from_update;
break;
}
default:
UNREACHABLE();
}
}
}
bool MessagesManager::is_message_unload_enabled() const {
return G()->parameters().use_message_db || td_->auth_manager_->is_bot();
}
bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) const {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
// don't want to unload messages from opened dialogs
// don't want to unload messages to which there are replies in yet unsent messages
// don't want to unload message with active reply markup
// don't want to unload the newest pinned message
// don't want to unload last edited message, because server can send updateEditChannelMessage again
// don't want to unload messages from the last album
// can't unload from memory last dialog, last database messages, yet unsent messages, being edited media messages and active live locations
// can't unload messages in dialog with active suffix load query
FullMessageId full_message_id{d->dialog_id, m->message_id};
return !d->is_opened && m->message_id != d->last_message_id && m->message_id != d->last_database_message_id &&
!m->message_id.is_yet_unsent() && active_live_location_full_message_ids_.count(full_message_id) == 0 &&
replied_by_yet_unsent_messages_.count(full_message_id) == 0 && m->edited_content == nullptr &&
d->suffix_load_queries_.empty() && m->message_id != d->reply_markup_message_id &&
m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id &&
(m->media_album_id != d->last_media_album_id || m->media_album_id == 0);
}
unique_ptr<MessagesManager::Message> MessagesManager::unload_message(Dialog *d, MessageId message_id) {
CHECK(d != nullptr);
CHECK(message_id.is_valid());
bool need_update_dialog_pos = false;
auto m = do_delete_message(d, message_id, false, true, &need_update_dialog_pos, "unload_message");
CHECK(!need_update_dialog_pos);
return m;
}
unique_ptr<MessagesManager::Message> MessagesManager::delete_message(Dialog *d, MessageId message_id,
bool is_permanently_deleted,
bool *need_update_dialog_pos, const char *source) {
return do_delete_message(d, message_id, is_permanently_deleted, false, need_update_dialog_pos, source);
}
void MessagesManager::add_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) {
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent());
auto it = d->random_id_to_message_id.find(random_id);
if (it == d->random_id_to_message_id.end() || it->second.get() < message_id.get()) {
LOG(INFO) << "Add correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id;
d->random_id_to_message_id[random_id] = message_id;
}
}
void MessagesManager::delete_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) {
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent());
auto it = d->random_id_to_message_id.find(random_id);
if (it != d->random_id_to_message_id.end() && it->second == message_id) {
LOG(INFO) << "Delete correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id;
d->random_id_to_message_id.erase(it);
}
}
void MessagesManager::add_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
MessageId message_id) {
CHECK(d != nullptr);
CHECK(notification_id.is_valid());
CHECK(message_id.is_valid());
auto it = d->notification_id_to_message_id.find(notification_id);
if (it == d->notification_id_to_message_id.end()) {
VLOG(notifications) << "Add correspondence from " << notification_id << " to " << message_id << " in "
<< d->dialog_id;
d->notification_id_to_message_id.emplace(notification_id, message_id);
} else if (it->second != message_id) {
LOG(ERROR) << "Have duplicated " << notification_id << " in " << d->dialog_id << " in " << message_id << " and "
<< it->second;
if (it->second < message_id) {
it->second = message_id;
}
}
}
void MessagesManager::delete_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
MessageId message_id) {
CHECK(d != nullptr);
CHECK(notification_id.is_valid());
CHECK(message_id.is_valid());
auto it = d->notification_id_to_message_id.find(notification_id);
if (it != d->notification_id_to_message_id.end() && it->second == message_id) {
VLOG(notifications) << "Delete correspondence from " << notification_id << " to " << message_id << " in "
<< d->dialog_id;
d->notification_id_to_message_id.erase(it);
} else {
LOG(ERROR) << "Can't find " << notification_id << " in " << d->dialog_id << " with " << message_id;
}
}
void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update,
bool ignore_pinned_message_notification_removal) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
if (!m->notification_id.is_valid()) {
return;
}
auto from_mentions = is_from_mention_notification_group(m);
auto &group_info = get_notification_group_info(d, m);
if (!group_info.group_id.is_valid()) {
return;
}
bool had_active_notification = is_message_notification_active(d, m);
auto notification_id = m->notification_id;
VLOG(notifications) << "Remove " << notification_id << " from " << m->message_id << " in " << group_info.group_id
<< '/' << d->dialog_id << " from database, was_active = " << had_active_notification
<< ", is_permanent = " << is_permanent;
delete_notification_id_to_message_id_correspondence(d, notification_id, m->message_id);
m->removed_notification_id = m->notification_id;
m->notification_id = NotificationId();
if (d->pinned_message_notification_message_id == m->message_id && is_permanent &&
!ignore_pinned_message_notification_removal) {
remove_dialog_pinned_message_notification(
d, "remove_message_notification_id"); // must be called after notification_id is removed
}
if (group_info.last_notification_id == notification_id) {
// last notification is deleted, need to find new last notification
fix_dialog_last_notification_id(d, from_mentions, m->message_id);
}
if (is_permanent) {
if (had_active_notification) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id,
notification_id, is_permanent, force_update, Promise<Unit>(),
"remove_message_notification_id");
}
// on_message_changed will be called by the caller
// don't need to call there to not save twice/or to save just deleted message
} else {
on_message_changed(d, m, false, "remove_message_notification_id");
}
}
void MessagesManager::remove_new_secret_chat_notification(Dialog *d, bool is_permanent) {
CHECK(d != nullptr);
auto notification_id = d->new_secret_chat_notification_id;
CHECK(notification_id.is_valid());
VLOG(notifications) << "Remove " << notification_id << " about new secret " << d->dialog_id << " from "
<< d->message_notification_group.group_id;
d->new_secret_chat_notification_id = NotificationId();
bool is_fixed = set_dialog_last_notification(d->dialog_id, d->message_notification_group, 0, NotificationId(),
"remove_new_secret_chat_notification");
CHECK(is_fixed);
if (is_permanent) {
CHECK(d->message_notification_group.group_id.is_valid());
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification,
d->message_notification_group.group_id, notification_id, true, true, Promise<Unit>(),
"remove_new_secret_chat_notification");
}
}
void MessagesManager::fix_dialog_last_notification_id(Dialog *d, bool from_mentions, MessageId message_id) {
CHECK(d != nullptr);
CHECK(!message_id.is_scheduled());
MessagesConstIterator it(d, message_id);
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
VLOG(notifications) << "Trying to fix last notification identifier in " << group_info.group_id << " from "
<< d->dialog_id << " from " << message_id << "/" << group_info.last_notification_id;
if (*it != nullptr && ((*it)->message_id == message_id || (*it)->have_next)) {
while (*it != nullptr) {
const Message *m = *it;
if (is_from_mention_notification_group(m) == from_mentions && m->notification_id.is_valid() &&
is_message_notification_active(d, m) && m->message_id != message_id) {
bool is_fixed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id,
"fix_dialog_last_notification_id");
CHECK(is_fixed);
return;
}
--it;
}
}
if (G()->parameters().use_message_db) {
get_message_notifications_from_database(
d->dialog_id, group_info.group_id, group_info.last_notification_id, message_id, 1,
PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions,
prev_last_notification_id = group_info.last_notification_id](Result<vector<Notification>> result) {
send_closure(actor_id, &MessagesManager::do_fix_dialog_last_notification_id, dialog_id, from_mentions,
prev_last_notification_id, std::move(result));
}));
}
}
void MessagesManager::do_fix_dialog_last_notification_id(DialogId dialog_id, bool from_mentions,
NotificationId prev_last_notification_id,
Result<vector<Notification>> result) {
if (result.is_error()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
VLOG(notifications) << "Receive " << result.ok().size() << " message notifications in " << group_info.group_id << '/'
<< dialog_id << " from " << prev_last_notification_id;
if (group_info.last_notification_id != prev_last_notification_id) {
// last_notification_id was changed
return;
}
auto notifications = result.move_as_ok();
CHECK(notifications.size() <= 1);
int32 last_notification_date = 0;
NotificationId last_notification_id;
if (!notifications.empty()) {
last_notification_date = notifications[0].date;
last_notification_id = notifications[0].notification_id;
}
bool is_fixed = set_dialog_last_notification(dialog_id, group_info, last_notification_date, last_notification_id,
"do_fix_dialog_last_notification_id");
CHECK(is_fixed);
}
// DO NOT FORGET TO ADD ALL CHANGES OF THIS FUNCTION AS WELL TO do_delete_all_dialog_messages
unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *d, MessageId message_id,
bool is_permanently_deleted,
bool only_from_memory,
bool *need_update_dialog_pos,
const char *source) {
CHECK(d != nullptr);
if (!message_id.is_valid()) {
if (message_id.is_valid_scheduled()) {
return do_delete_scheduled_message(d, message_id, is_permanently_deleted, source);
}
LOG(ERROR) << "Trying to delete " << message_id << " in " << d->dialog_id << " from " << source;
return nullptr;
}
FullMessageId full_message_id(d->dialog_id, message_id);
unique_ptr<Message> *v = treap_find_message(&d->messages, message_id);
if (*v == nullptr) {
LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source;
if (only_from_memory) {
return nullptr;
}
if (get_message_force(d, message_id, "do_delete_message") == nullptr) {
// currently there may be a race between add_message_to_database and get_message_force,
// so delete a message from database just in case
delete_message_from_database(d, message_id, nullptr, is_permanently_deleted);
if (is_permanently_deleted && d->last_clear_history_message_id == message_id) {
set_dialog_last_clear_history_date(d, 0, MessageId(), "do_delete_message");
*need_update_dialog_pos = true;
}
/*
can't do this because the message may be never received in the dialog, unread count will became negative
// if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not
if (message_id.is_valid() && !message_id.is_yet_unsent() && d->is_last_read_inbox_message_id_inited &&
message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count;
if (unread_count == 0) {
LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id
<< ". Last read is " << d->last_read_inbox_message_id;
dump_debug_message_op(d, 3);
} else {
unread_count--;
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
source);
}
}
*/
return nullptr;
}
v = treap_find_message(&d->messages, message_id);
CHECK(*v != nullptr);
}
const Message *m = v->get();
CHECK(m->message_id == message_id);
if (only_from_memory && !can_unload_message(d, m)) {
return nullptr;
}
LOG_CHECK(!d->being_deleted_message_id.is_valid())
<< d->being_deleted_message_id << " " << message_id << " " << source;
d->being_deleted_message_id = message_id;
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_type(), false,
m->have_previous, m->have_next, source);
}
bool need_get_history = false;
if (!only_from_memory) {
LOG(INFO) << "Deleting " << full_message_id << " with have_previous = " << m->have_previous
<< " and have_next = " << m->have_next << " from " << source;
delete_message_from_database(d, message_id, m, is_permanently_deleted);
delete_active_live_location(d->dialog_id, m);
remove_message_file_sources(d->dialog_id, m);
if (message_id == d->last_message_id) {
MessagesConstIterator it(d, message_id);
CHECK(*it == m);
if ((*it)->have_previous) {
--it;
if (*it != nullptr) {
set_dialog_last_message_id(d, (*it)->message_id, "do_delete_message", *it);
} else {
LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
<< source;
dump_debug_message_op(d);
set_dialog_last_message_id(d, MessageId(), "do_delete_message");
}
} else {
need_get_history = true;
set_dialog_last_message_id(d, MessageId(), "do_delete_message");
d->delete_last_message_date = m->date;
d->deleted_last_message_id = message_id;
d->is_last_message_deleted_locally = Slice(source) == Slice(DELETE_MESSAGE_USER_REQUEST_SOURCE);
on_dialog_updated(d->dialog_id, "do delete last message");
}
*need_update_dialog_pos = true;
}
if (message_id == d->last_database_message_id) {
MessagesConstIterator it(d, message_id);
CHECK(*it == m);
while ((*it)->have_previous) {
--it;
if (*it == nullptr || !(*it)->message_id.is_yet_unsent()) {
break;
}
}
if (*it != nullptr) {
if (!(*it)->message_id.is_yet_unsent() && (*it)->message_id != d->last_database_message_id) {
if ((*it)->message_id < d->first_database_message_id && d->dialog_id.get_type() == DialogType::Channel) {
// possible if messages was deleted from database, but not from memory after updateChannelTooLong
set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 1");
} else {
set_dialog_last_database_message_id(d, (*it)->message_id, "do_delete_message 2");
if (d->last_database_message_id < d->first_database_message_id) {
LOG(ERROR) << "Last database " << d->last_database_message_id << " became less than first database "
<< d->first_database_message_id << " after deletion of " << full_message_id;
set_dialog_first_database_message_id(d, d->last_database_message_id, "do_delete_message 2");
}
}
} else if (d->first_database_message_id == d->last_database_message_id) {
// database definitely has no more messages
set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 3");
} else {
LOG(INFO) << "Need to get history to repair last_database_message_id in " << d->dialog_id;
need_get_history = true;
}
} else {
LOG(ERROR) << "Have have_previous is true, but there is no previous";
dump_debug_message_op(d);
}
}
if (d->last_database_message_id.is_valid()) {
CHECK(d->first_database_message_id.is_valid());
} else {
set_dialog_first_database_message_id(d, MessageId(), "do_delete_message");
}
if (message_id == d->suffix_load_first_message_id_) {
MessagesConstIterator it(d, message_id);
CHECK(*it == m);
if ((*it)->have_previous) {
--it;
if (*it != nullptr) {
d->suffix_load_first_message_id_ = (*it)->message_id;
} else {
LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
<< source;
dump_debug_message_op(d);
d->suffix_load_first_message_id_ = MessageId();
d->suffix_load_done_ = false;
}
} else {
d->suffix_load_first_message_id_ = MessageId();
d->suffix_load_done_ = false;
}
}
}
if (only_from_memory && message_id >= d->suffix_load_first_message_id_) {
d->suffix_load_first_message_id_ = MessageId();
d->suffix_load_done_ = false;
}
if (m->have_previous && (only_from_memory || !m->have_next)) {
MessagesIterator it(d, message_id);
CHECK(*it == m);
--it;
Message *prev_m = *it;
if (prev_m != nullptr) {
prev_m->have_next = false;
} else {
LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
<< source;
dump_debug_message_op(d);
}
}
if ((*v)->have_next && (only_from_memory || !(*v)->have_previous)) {
MessagesIterator it(d, message_id);
CHECK(*it == m);
++it;
Message *next_m = *it;
if (next_m != nullptr) {
next_m->have_previous = false;
} else {
LOG(ERROR) << "Have have_next is true, but there is no next for " << full_message_id << " from " << source;
dump_debug_message_op(d);
}
}
auto result = treap_delete_message(v);
d->being_deleted_message_id = MessageId();
if (!only_from_memory) {
if (need_get_history && !td_->auth_manager_->is_bot() && have_input_peer(d->dialog_id, AccessRights::Read)) {
send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, d->dialog_id, true, false,
Promise<Unit>());
}
if (d->reply_markup_message_id == message_id) {
set_dialog_reply_markup(d, MessageId());
}
// if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not
if (has_incoming_notification(d->dialog_id, result.get()) && message_id > d->last_read_inbox_message_id &&
d->is_last_read_inbox_message_id_inited && !td_->auth_manager_->is_bot()) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count;
if (unread_count == 0) {
if (need_unread_counter(d->order)) {
LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id
<< ". Last read is " << d->last_read_inbox_message_id;
dump_debug_message_op(d, 3);
}
} else {
unread_count--;
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
source);
}
}
if (result->contains_unread_mention) {
if (d->unread_mention_count == 0) {
if (is_dialog_inited(d)) {
LOG(ERROR) << "Unread mention count became negative in " << d->dialog_id << " after deletion of "
<< message_id;
}
} else {
set_dialog_unread_mention_count(d, d->unread_mention_count - 1);
send_update_chat_unread_mention_count(d);
}
}
if (has_unread_message_reactions(d->dialog_id, result.get())) {
if (d->unread_reaction_count == 0) {
if (is_dialog_inited(d)) {
LOG(ERROR) << "Unread reaction count became negative in " << d->dialog_id << " after deletion of "
<< message_id;
}
} else {
set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1);
send_update_chat_unread_reaction_count(d, "do_delete_message");
}
}
update_message_count_by_index(d, -1, result.get());
update_reply_count_by_message(d, -1, result.get());
}
on_message_deleted(d, result.get(), is_permanently_deleted, source);
return result;
}
void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanently_deleted, const char *source) {
// also called for unloaded messages, but not for scheduled messages
CHECK(m->message_id.is_valid());
if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid()) {
auto it = d->yet_unsent_thread_message_ids.find(m->top_thread_message_id);
CHECK(it != d->yet_unsent_thread_message_ids.end());
auto is_deleted = it->second.erase(m->message_id) > 0;
CHECK(is_deleted);
if (it->second.empty()) {
d->yet_unsent_thread_message_ids.erase(it);
}
}
if (d->is_opened) {
auto it = dialog_viewed_messages_.find(d->dialog_id);
if (it != dialog_viewed_messages_.end()) {
auto &info = it->second;
CHECK(info != nullptr);
auto message_it = info->message_id_to_view_id.find(m->message_id);
if (message_it != info->message_id_to_view_id.end()) {
info->recently_viewed_messages.erase(message_it->second);
info->message_id_to_view_id.erase(message_it);
}
}
}
cancel_send_deleted_message(d->dialog_id, m, is_permanently_deleted);
auto dialog_type = d->dialog_id.get_type();
switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
if (m->message_id.is_server()) {
message_id_to_dialog_id_.erase(m->message_id);
}
break;
case DialogType::Channel:
case DialogType::SecretChat:
// nothing to do
break;
case DialogType::None:
default:
UNREACHABLE();
}
ttl_unregister_message(d->dialog_id, m, source);
ttl_period_unregister_message(d->dialog_id, m);
delete_bot_command_message_id(d->dialog_id, m->message_id);
unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_deleted");
unregister_message_reply(d->dialog_id, m);
if (m->notification_id.is_valid()) {
delete_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id);
}
if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) {
delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
}
if (m->is_topic_message) {
td_->forum_topic_manager_->on_topic_message_count_changed(d->dialog_id, m->top_thread_message_id, -1);
}
added_message_count_--;
}
bool MessagesManager::is_deleted_message(const Dialog *d, MessageId message_id) {
if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
return d->deleted_scheduled_server_message_ids.count(message_id.get_scheduled_server_message_id()) > 0;
} else {
return d->deleted_message_ids.count(message_id) > 0;
}
}
unique_ptr<MessagesManager::Message> MessagesManager::do_delete_scheduled_message(Dialog *d, MessageId message_id,
bool is_permanently_deleted,
const char *source) {
CHECK(d != nullptr);
LOG_CHECK(message_id.is_valid_scheduled()) << d->dialog_id << ' ' << message_id << ' ' << source;
unique_ptr<Message> *v = treap_find_message(&d->scheduled_messages, message_id);
if (*v == nullptr) {
LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source;
auto message = get_message_force(d, message_id, "do_delete_scheduled_message");
if (message == nullptr) {
// currently there may be a race between add_message_to_database and get_message_force,
// so delete a message from database just in case
delete_message_from_database(d, message_id, nullptr, is_permanently_deleted);
return nullptr;
}
message_id = message->message_id;
v = treap_find_message(&d->scheduled_messages, message_id);
CHECK(*v != nullptr);
}
const Message *m = v->get();
CHECK(m->message_id == message_id);
LOG(INFO) << "Deleting " << FullMessageId{d->dialog_id, message_id} << " from " << source;
delete_message_from_database(d, message_id, m, is_permanently_deleted);
remove_message_file_sources(d->dialog_id, m);
auto result = treap_delete_message(v);
CHECK(m == result.get());
if (message_id.is_scheduled_server()) {
size_t erased_count = d->scheduled_message_date.erase(message_id.get_scheduled_server_message_id());
CHECK(erased_count != 0);
}
cancel_send_deleted_message(d->dialog_id, result.get(), is_permanently_deleted);
unregister_message_content(td_, m->content.get(), {d->dialog_id, message_id}, "do_delete_scheduled_message");
unregister_message_reply(d->dialog_id, m);
if (message_id.is_yet_unsent()) {
delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
}
if (m->is_topic_message) {
td_->forum_topic_manager_->on_topic_message_count_changed(d->dialog_id, m->top_thread_message_id, -1);
}
return result;
}
void MessagesManager::do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &message,
bool is_permanently_deleted, vector<int64> &deleted_message_ids) {
if (message == nullptr) {
return;
}
const Message *m = message.get();
MessageId message_id = m->message_id;
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_type(), false,
m->have_previous, m->have_next, "delete all messages");
}
LOG(INFO) << "Delete " << message_id;
deleted_message_ids.push_back(message_id.get());
do_delete_all_dialog_messages(d, message->right, is_permanently_deleted, deleted_message_ids);
do_delete_all_dialog_messages(d, message->left, is_permanently_deleted, deleted_message_ids);
delete_active_live_location(d->dialog_id, m);
remove_message_file_sources(d->dialog_id, m);
on_message_deleted(d, message.get(), is_permanently_deleted, "do_delete_all_dialog_messages");
message = nullptr;
}
bool MessagesManager::have_dialog(DialogId dialog_id) const {
return dialogs_.count(dialog_id) > 0;
}
void MessagesManager::load_dialogs(vector<DialogId> dialog_ids, Promise<vector<DialogId>> &&promise) {
LOG(INFO) << "Load chats " << format::as_array(dialog_ids);
Dependencies dependencies;
for (auto dialog_id : dialog_ids) {
if (dialog_id.is_valid() && !have_dialog(dialog_id)) {
dependencies.add_dialog_dependencies(dialog_id);
}
}
dependencies.resolve_force(td_, "load_dialogs");
td::remove_if(dialog_ids, [this](DialogId dialog_id) { return !have_dialog_info(dialog_id); });
for (auto dialog_id : dialog_ids) {
force_create_dialog(dialog_id, "load_dialogs");
}
LOG(INFO) << "Loaded chats " << format::as_array(dialog_ids);
promise.set_value(std::move(dialog_ids));
}
bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise<Unit> &&promise) {
if (!dialog_id.is_valid()) {
promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
return false;
}
if (!have_dialog_force(dialog_id, "load_dialog")) { // TODO remove _force
if (G()->parameters().use_message_db) {
// TODO load dialog from database, DialogLoader
// send_closure_later(actor_id(this), &MessagesManager::load_dialog_from_database, dialog_id,
// std::move(promise));
// return false;
}
if (td_->auth_manager_->is_bot()) {
switch (dialog_id.get_type()) {
case DialogType::User: {
auto user_id = dialog_id.get_user_id();
auto have_user = td_->contacts_manager_->get_user(user_id, left_tries, std::move(promise));
if (!have_user) {
return false;
}
break;
}
case DialogType::Chat: {
auto have_chat = td_->contacts_manager_->get_chat(dialog_id.get_chat_id(), left_tries, std::move(promise));
if (!have_chat) {
return false;
}
break;
}
case DialogType::Channel: {
auto have_channel =
td_->contacts_manager_->get_channel(dialog_id.get_channel_id(), left_tries, std::move(promise));
if (!have_channel) {
return false;
}
break;
}
case DialogType::SecretChat:
promise.set_error(Status::Error(400, "Chat not found"));
return false;
case DialogType::None:
default:
UNREACHABLE();
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return false;
}
add_dialog(dialog_id, "load_dialog");
return true;
}
promise.set_error(Status::Error(400, "Chat not found"));
return false;
}
promise.set_value(Unit());
return true;
}
void MessagesManager::load_dialog_filter_dialogs(DialogFilterId dialog_filter_id,
vector<InputDialogId> &&input_dialog_ids, Promise<Unit> &&promise) {
const size_t MAX_SLICE_SIZE = 100; // server side limit
MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"};
mpas.add_promise(std::move(promise));
auto lock = mpas.get_promise();
for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) {
auto end_i = i + MAX_SLICE_SIZE;
auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end();
vector<InputDialogId> slice_input_dialog_ids = {input_dialog_ids.begin() + i, end};
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id,
dialog_ids = InputDialogId::get_dialog_ids(slice_input_dialog_ids),
promise = mpas.get_promise()](Result<Unit> &&result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
send_closure(actor_id, &MessagesManager::on_load_dialog_filter_dialogs, dialog_filter_id, std::move(dialog_ids),
std::move(promise));
});
td_->create_handler<GetDialogsQuery>(std::move(query_promise))->send(std::move(slice_input_dialog_ids));
}
lock.set_value(Unit());
}
void MessagesManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<DialogId> &&dialog_ids,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
td::remove_if(dialog_ids,
[this](DialogId dialog_id) { return have_dialog_force(dialog_id, "on_load_dialog_filter_dialogs"); });
if (dialog_ids.empty()) {
LOG(INFO) << "All chats from " << dialog_filter_id << " were loaded";
return promise.set_value(Unit());
}
LOG(INFO) << "Failed to load chats " << dialog_ids << " from " << dialog_filter_id;
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
if (old_dialog_filter == nullptr) {
return promise.set_value(Unit());
}
CHECK(is_update_chat_filters_sent_);
delete_dialogs_from_filter(old_dialog_filter, std::move(dialog_ids), "on_load_dialog_filter_dialogs");
promise.set_value(Unit());
}
void MessagesManager::load_dialog_filter(DialogFilterId dialog_filter_id, bool force, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
if (!dialog_filter_id.is_valid()) {
return promise.set_error(Status::Error(400, "Invalid chat filter identifier specified"));
}
auto filter = get_dialog_filter(dialog_filter_id);
if (filter == nullptr) {
return promise.set_value(Unit());
}
load_dialog_filter(filter, force, std::move(promise));
}
void MessagesManager::load_dialog_filter(const DialogFilter *filter, bool force, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
vector<InputDialogId> needed_dialog_ids;
for (auto input_dialog_ids :
{&filter->pinned_dialog_ids, &filter->excluded_dialog_ids, &filter->included_dialog_ids}) {
for (const auto &input_dialog_id : *input_dialog_ids) {
if (!have_dialog(input_dialog_id.get_dialog_id())) {
needed_dialog_ids.push_back(input_dialog_id);
}
}
}
vector<InputDialogId> input_dialog_ids;
for (const auto &input_dialog_id : needed_dialog_ids) {
auto dialog_id = input_dialog_id.get_dialog_id();
// TODO load dialogs asynchronously
if (!have_dialog_force(dialog_id, "load_dialog_filter")) {
if (dialog_id.get_type() == DialogType::SecretChat) {
if (have_dialog_info_force(dialog_id)) {
force_create_dialog(dialog_id, "load_dialog_filter");
}
} else {
input_dialog_ids.push_back(input_dialog_id);
}
}
}
if (!input_dialog_ids.empty() && !force) {
return load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise));
}
promise.set_value(Unit());
}
void MessagesManager::delete_dialogs_from_filter(const DialogFilter *dialog_filter, vector<DialogId> &&dialog_ids,
const char *source) {
auto new_dialog_filter = td::make_unique<DialogFilter>(*dialog_filter);
for (auto dialog_id : dialog_ids) {
InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id);
InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id);
InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
}
if (*new_dialog_filter != *dialog_filter) {
LOG(INFO) << "Update " << dialog_filter->dialog_filter_id << " from " << *dialog_filter << " to "
<< *new_dialog_filter;
edit_dialog_filter(std::move(new_dialog_filter), source);
save_dialog_filters();
send_update_chat_filters();
synchronize_dialog_filters();
}
}
void MessagesManager::get_recommended_dialog_filters(
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto query_promise =
PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
Result<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> result) mutable {
send_closure(actor_id, &MessagesManager::on_get_recommended_dialog_filters, std::move(result),
std::move(promise));
});
td_->create_handler<GetSuggestedDialogFiltersQuery>(std::move(query_promise))->send();
}
void MessagesManager::on_get_recommended_dialog_filters(
Result<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> result,
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
CHECK(!td_->auth_manager_->is_bot());
auto suggested_filters = result.move_as_ok();
MultiPromiseActorSafe mpas{"LoadRecommendedFiltersMultiPromiseActor"};
mpas.add_promise(Promise<Unit>());
auto lock = mpas.get_promise();
vector<RecommendedDialogFilter> filters;
for (auto &suggested_filter : suggested_filters) {
RecommendedDialogFilter filter;
filter.dialog_filter = DialogFilter::get_dialog_filter(std::move(suggested_filter->filter_), false);
CHECK(filter.dialog_filter != nullptr);
filter.dialog_filter->dialog_filter_id = DialogFilterId(); // just in case
load_dialog_filter(filter.dialog_filter.get(), false, mpas.get_promise());
filter.description = std::move(suggested_filter->description_);
filters.push_back(std::move(filter));
}
mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), filters = std::move(filters),
promise = std::move(promise)](Result<Unit> &&result) mutable {
send_closure(actor_id, &MessagesManager::on_load_recommended_dialog_filters, std::move(result), std::move(filters),
std::move(promise));
}));
lock.set_value(Unit());
}
void MessagesManager::on_load_recommended_dialog_filters(
Result<Unit> &&result, vector<RecommendedDialogFilter> &&filters,
Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
CHECK(!td_->auth_manager_->is_bot());
auto chat_filters = transform(filters, [this](const RecommendedDialogFilter &filter) {
return td_api::make_object<td_api::recommendedChatFilter>(get_chat_filter_object(filter.dialog_filter.get()),
filter.description);
});
recommended_dialog_filters_ = std::move(filters);
promise.set_value(td_api::make_object<td_api::recommendedChatFilters>(std::move(chat_filters)));
}
Result<DialogDate> MessagesManager::get_dialog_list_last_date(DialogListId dialog_list_id) {
CHECK(!td_->auth_manager_->is_bot());
auto *list_ptr = get_dialog_list(dialog_list_id);
if (list_ptr == nullptr) {
return Status::Error(400, "Chat list not found");
}
return list_ptr->list_last_dialog_date_;
}
vector<DialogId> MessagesManager::get_dialogs(DialogListId dialog_list_id, DialogDate offset, int32 limit,
bool exact_limit, bool force, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto *list_ptr = get_dialog_list(dialog_list_id);
if (list_ptr == nullptr) {
promise.set_error(Status::Error(400, "Chat list not found"));
return {};
}
auto &list = *list_ptr;
LOG(INFO) << "Get chats in " << dialog_list_id << " with offset " << offset << " and limit " << limit
<< ". last_dialog_date = " << list.list_last_dialog_date_
<< ", last_pinned_dialog_date_ = " << list.last_pinned_dialog_date_
<< ", are_pinned_dialogs_inited_ = " << list.are_pinned_dialogs_inited_;
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
vector<DialogId> result;
if (dialog_list_id == DialogListId(FolderId::main()) && sponsored_dialog_id_.is_valid()) {
auto d = get_dialog(sponsored_dialog_id_);
CHECK(d != nullptr);
if (is_dialog_sponsored(d)) {
DialogDate date(get_dialog_private_order(&list, d), d->dialog_id);
if (offset < date) {
result.push_back(sponsored_dialog_id_);
offset = date;
limit--;
}
}
}
if (!list.are_pinned_dialogs_inited_) {
if (limit == 0 || force) {
promise.set_value(Unit());
return result;
} else {
if (dialog_list_id.is_folder()) {
auto &folder = *get_dialog_folder(dialog_list_id.get_folder_id());
if (folder.last_loaded_database_dialog_date_ == folder.last_database_server_dialog_date_ &&
folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
load_dialog_list(list, limit, std::move(promise));
return {};
}
}
reload_pinned_dialogs(dialog_list_id, std::move(promise));
return {};
}
}
if (dialog_list_id.is_filter()) {
auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
CHECK(filter != nullptr);
vector<InputDialogId> input_dialog_ids;
for (const auto &input_dialog_id : filter->pinned_dialog_ids) {
auto dialog_id = input_dialog_id.get_dialog_id();
if (!have_dialog_force(dialog_id, "get_dialogs")) {
if (dialog_id.get_type() == DialogType::SecretChat) {
if (have_dialog_info_force(dialog_id)) {
force_create_dialog(dialog_id, "get_dialogs");
}
} else {
input_dialog_ids.push_back(input_dialog_id);
}
}
}
if (!input_dialog_ids.empty()) {
if (limit == 0 || force) {
promise.set_value(Unit());
return result;
} else {
load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise));
return {};
}
}
}
if (!list.pinned_dialogs_.empty() && offset < list.pinned_dialogs_.back() && limit > 0) {
bool need_reload_pinned_dialogs = false;
bool need_remove_unknown_secret_chats = false;
for (auto &pinned_dialog : list.pinned_dialogs_) {
if (offset < pinned_dialog) {
auto dialog_id = pinned_dialog.get_dialog_id();
auto d = get_dialog_force(dialog_id, "get_dialogs");
if (d == nullptr) {
LOG(ERROR) << "Failed to load pinned " << dialog_id << " from " << dialog_list_id;
if (dialog_id.get_type() != DialogType::SecretChat) {
need_reload_pinned_dialogs = true;
} else {
need_remove_unknown_secret_chats = true;
}
continue;
}
if (d->order == DEFAULT_ORDER) {
LOG(INFO) << "Loaded pinned " << dialog_id << " with default order in " << dialog_list_id;
continue;
}
result.push_back(dialog_id);
offset = pinned_dialog;
limit--;
if (limit == 0) {
break;
}
}
}
if (need_reload_pinned_dialogs) {
reload_pinned_dialogs(dialog_list_id, Auto());
}
if (need_remove_unknown_secret_chats) {
td::remove_if(list.pinned_dialogs_, [this, &list](const DialogDate &dialog_date) {
auto dialog_id = dialog_date.get_dialog_id();
if (dialog_id.get_type() == DialogType::SecretChat && !have_dialog_force(dialog_id, "get_dialogs 2")) {
list.pinned_dialog_id_orders_.erase(dialog_id);
return true;
}
return false;
});
save_pinned_folder_dialog_ids(list);
}
}
update_list_last_pinned_dialog_date(list);
vector<const DialogFolder *> folders;
vector<std::set<DialogDate>::const_iterator> folder_iterators;
for (auto folder_id : get_dialog_list_folder_ids(list)) {
folders.push_back(get_dialog_folder(folder_id));
folder_iterators.push_back(folders.back()->ordered_dialogs_.upper_bound(offset));
}
while (limit > 0) {
size_t best_pos = 0;
DialogDate best_dialog_date = MAX_DIALOG_DATE;
for (size_t i = 0; i < folders.size(); i++) {
while (folder_iterators[i] != folders[i]->ordered_dialogs_.end() &&
*folder_iterators[i] <= list.list_last_dialog_date_ &&
(!is_dialog_in_list(get_dialog(folder_iterators[i]->get_dialog_id()), dialog_list_id) ||
get_dialog_pinned_order(&list, folder_iterators[i]->get_dialog_id()) != DEFAULT_ORDER)) {
++folder_iterators[i];
}
if (folder_iterators[i] != folders[i]->ordered_dialogs_.end() &&
*folder_iterators[i] <= list.list_last_dialog_date_ && *folder_iterators[i] < best_dialog_date) {
best_pos = i;
best_dialog_date = *folder_iterators[i];
}
}
if (best_dialog_date == MAX_DIALOG_DATE || best_dialog_date.get_order() == DEFAULT_ORDER) {
break;
}
limit--;
result.push_back(folder_iterators[best_pos]->get_dialog_id());
++folder_iterators[best_pos];
}
if ((!result.empty() && (!exact_limit || limit == 0)) || force || list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
if (limit > 0 && list.list_last_dialog_date_ != MAX_DIALOG_DATE) {
LOG(INFO) << "Preload next " << limit << " chats in " << dialog_list_id;
load_dialog_list(list, limit, Promise<Unit>());
}
promise.set_value(Unit());
return result;
} else {
if (!result.empty()) {
LOG(INFO) << "Have only " << result.size() << " chats, but " << limit << " chats more are needed";
}
load_dialog_list(list, limit, std::move(promise));
return {};
}
}
void MessagesManager::load_dialog_list(DialogList &list, int32 limit, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
if (limit > MAX_GET_DIALOGS + 2) {
limit = MAX_GET_DIALOGS + 2;
}
bool is_request_sent = false;
for (auto folder_id : get_dialog_list_folder_ids(list)) {
const auto &folder = *get_dialog_folder(folder_id);
if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
load_folder_dialog_list(folder_id, limit, false);
is_request_sent = true;
}
}
if (is_request_sent) {
LOG(INFO) << "Wait for loading of " << limit << " chats in " << list.dialog_list_id;
list.load_list_queries_.push_back(std::move(promise));
} else {
LOG(ERROR) << "There is nothing to load for " << list.dialog_list_id << " with folders "
<< get_dialog_list_folder_ids(list);
promise.set_error(Status::Error(404, "Not Found"));
}
}
void MessagesManager::load_folder_dialog_list(FolderId folder_id, int32 limit, bool only_local) {
if (G()->close_flag()) {
return;
}
CHECK(!td_->auth_manager_->is_bot());
auto &folder = *get_dialog_folder(folder_id);
if (folder.folder_last_dialog_date_ == MAX_DIALOG_DATE) {
return;
}
bool use_database = G()->parameters().use_message_db &&
folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_;
if (only_local && !use_database) {
return;
}
auto &multipromise = folder.load_folder_dialog_list_multipromise_;
if (multipromise.promise_count() != 0) {
// queries have already been sent, just wait for the result
LOG(INFO) << "Skip loading of dialog list in " << folder_id << " with limit " << limit
<< ", because it is already being loaded";
if (use_database && folder.load_dialog_list_limit_max_ != 0) {
folder.load_dialog_list_limit_max_ = max(folder.load_dialog_list_limit_max_, limit);
}
return;
}
LOG(INFO) << "Load chat list in " << folder_id << " with limit " << limit;
multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result<Unit> result) {
send_closure_later(actor_id, &MessagesManager::on_load_folder_dialog_list, folder_id, std::move(result));
}));
bool is_query_sent = false;
if (use_database) {
load_folder_dialog_list_from_database(folder_id, limit, multipromise.get_promise());
is_query_sent = true;
} else {
LOG(INFO) << "Get chats from " << folder.last_server_dialog_date_;
multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result<Unit> result) {
if (result.is_ok()) {
send_closure(actor_id, &MessagesManager::recalc_unread_count, DialogListId(folder_id), -1, true);
}
}));
auto lock = multipromise.get_promise();
reload_pinned_dialogs(DialogListId(folder_id), multipromise.get_promise());
if (folder.folder_last_dialog_date_ == folder.last_server_dialog_date_) {
td_->create_handler<GetDialogListQuery>(multipromise.get_promise())
->send(folder_id, folder.last_server_dialog_date_.get_date(),
folder.last_server_dialog_date_.get_message_id().get_next_server_message_id().get_server_message_id(),
folder.last_server_dialog_date_.get_dialog_id(), int32{MAX_GET_DIALOGS});
is_query_sent = true;
}
if (folder_id == FolderId::main() && folder.last_server_dialog_date_ == MIN_DIALOG_DATE) {
// do not pass promise to not wait for drafts before showing chat list
td_->create_handler<GetAllDraftsQuery>()->send();
}
lock.set_value(Unit());
}
CHECK(is_query_sent);
}
void MessagesManager::on_load_folder_dialog_list(FolderId folder_id, Result<Unit> &&result) {
if (G()->close_flag()) {
return;
}
CHECK(!td_->auth_manager_->is_bot());
const auto &folder = *get_dialog_folder(folder_id);
if (result.is_ok()) {
LOG(INFO) << "Successfully loaded chats in " << folder_id;
if (folder.last_server_dialog_date_ == MAX_DIALOG_DATE) {
return;
}
bool need_new_get_dialog_list = false;
for (const auto &list_it : dialog_lists_) {
auto &list = list_it.second;
if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) {
LOG(INFO) << "Need to load more chats in " << folder_id << " for " << list_it.first;
need_new_get_dialog_list = true;
}
}
if (need_new_get_dialog_list) {
load_folder_dialog_list(folder_id, int32{MAX_GET_DIALOGS}, false);
}
return;
}
LOG(WARNING) << "Failed to load chats in " << folder_id << ": " << result.error();
vector<Promise<Unit>> promises;
for (auto &list_it : dialog_lists_) {
auto &list = list_it.second;
if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) {
append(promises, std::move(list.load_list_queries_));
list.load_list_queries_.clear();
}
}
fail_promises(promises, result.move_as_error());
}
void MessagesManager::load_folder_dialog_list_from_database(FolderId folder_id, int32 limit, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto &folder = *get_dialog_folder(folder_id);
LOG(INFO) << "Load " << limit << " chats in " << folder_id << " from database from "
<< folder.last_loaded_database_dialog_date_
<< ", last database server dialog date = " << folder.last_database_server_dialog_date_;
CHECK(folder.load_dialog_list_limit_max_ == 0);
folder.load_dialog_list_limit_max_ = limit;
G()->td_db()->get_dialog_db_async()->get_dialogs(
folder_id, folder.last_loaded_database_dialog_date_.get_order(),
folder.last_loaded_database_dialog_date_.get_dialog_id(), limit,
PromiseCreator::lambda([actor_id = actor_id(this), folder_id, limit,
promise = std::move(promise)](DialogDbGetDialogsResult result) mutable {
send_closure(actor_id, &MessagesManager::on_get_dialogs_from_database, folder_id, limit, std::move(result),
std::move(promise));
}));
}
void MessagesManager::on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(!td_->auth_manager_->is_bot());
auto &folder = *get_dialog_folder(folder_id);
LOG(INFO) << "Receive " << dialogs.dialogs.size() << " from expected " << limit << " chats in " << folder_id
<< " in from database with next order " << dialogs.next_order << " and next " << dialogs.next_dialog_id;
int32 new_get_dialogs_limit = 0;
int32 have_more_dialogs_in_database = (limit == static_cast<int32>(dialogs.dialogs.size()));
if (have_more_dialogs_in_database && limit < folder.load_dialog_list_limit_max_) {
new_get_dialogs_limit = folder.load_dialog_list_limit_max_ - limit;
}
folder.load_dialog_list_limit_max_ = 0;
size_t dialogs_skipped = 0;
for (auto &dialog : dialogs.dialogs) {
Dialog *d = on_load_dialog_from_database(DialogId(), std::move(dialog), "on_get_dialogs_from_database");
if (d == nullptr) {
dialogs_skipped++;
continue;
}
if (d->folder_id != folder_id) {
LOG(WARNING) << "Skip " << d->dialog_id << " received from database, because it is in " << d->folder_id
<< " instead of " << folder_id;
dialogs_skipped++;
continue;
}
LOG(INFO) << "Loaded from database " << d->dialog_id << " with order " << d->order;
}
DialogDate max_dialog_date(dialogs.next_order, dialogs.next_dialog_id);
if (!have_more_dialogs_in_database) {
folder.last_loaded_database_dialog_date_ = MAX_DIALOG_DATE;
LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_;
folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_database_server_dialog_date_);
LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_;
update_last_dialog_date(folder_id);
} else if (folder.last_loaded_database_dialog_date_ < max_dialog_date) {
folder.last_loaded_database_dialog_date_ = min(max_dialog_date, folder.last_database_server_dialog_date_);
LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_;
folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_loaded_database_dialog_date_);
LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_;
update_last_dialog_date(folder_id);
for (const auto &list_it : dialog_lists_) {
auto &list = list_it.second;
if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder) && new_get_dialogs_limit < limit) {
new_get_dialogs_limit = limit;
}
}
} else {
LOG(ERROR) << "Last loaded database dialog date didn't increased, skipped " << dialogs_skipped << " chats out of "
<< dialogs.dialogs.size();
}
if (!(folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_)) {
// have_more_dialogs_in_database = false;
new_get_dialogs_limit = 0;
}
if (new_get_dialogs_limit == 0) {
preload_folder_dialog_list_timeout_.add_timeout_in(folder_id.get(), 0.2);
promise.set_value(Unit());
} else {
load_folder_dialog_list_from_database(folder_id, new_get_dialogs_limit, std::move(promise));
}
}
void MessagesManager::preload_folder_dialog_list(FolderId folder_id) {
if (G()->close_flag()) {
LOG(INFO) << "Skip chat list preload in " << folder_id << " because of closing";
return;
}
CHECK(!td_->auth_manager_->is_bot());
auto &folder = *get_dialog_folder(folder_id);
CHECK(G()->parameters().use_message_db);
if (folder.load_folder_dialog_list_multipromise_.promise_count() != 0) {
LOG(INFO) << "Skip chat list preload in " << folder_id << ", because there is a pending load chat list request";
return;
}
if (folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_) {
// if there are some dialogs in database, preload some of them
load_folder_dialog_list(folder_id, 20, true);
} else if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
// otherwise load more dialogs from the server
load_folder_dialog_list(folder_id, MAX_GET_DIALOGS, false);
} else {
recalc_unread_count(DialogListId(folder_id), -1, false);
}
}
void MessagesManager::get_dialogs_from_list(DialogListId dialog_list_id, int32 limit,
Promise<td_api::object_ptr<td_api::chats>> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
if (get_dialog_list(dialog_list_id) == nullptr) {
return promise.set_error(Status::Error(400, "Chat list not found"));
}
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
auto task_id = ++current_get_dialogs_task_id_;
auto &task = get_dialogs_tasks_[task_id];
task.dialog_list_id = dialog_list_id;
task.retry_count = 5;
task.limit = limit;
task.promise = std::move(promise);
get_dialogs_from_list_impl(task_id);
}
void MessagesManager::get_dialogs_from_list_impl(int64 task_id) {
auto task_it = get_dialogs_tasks_.find(task_id);
CHECK(task_it != get_dialogs_tasks_.end());
auto &task = task_it->second;
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Result<Unit> &&result) {
// on_get_dialogs_from_list can delete get_dialogs_tasks_[task_id], so it must be called later
send_closure_later(actor_id, &MessagesManager::on_get_dialogs_from_list, task_id, std::move(result));
});
auto dialog_ids = get_dialogs(task.dialog_list_id, MIN_DIALOG_DATE, task.limit, true, false, std::move(promise));
auto &list = *get_dialog_list(task.dialog_list_id);
auto total_count = get_dialog_total_count(list);
LOG(INFO) << "Receive " << dialog_ids.size() << " chats instead of " << task.limit << " out of " << total_count
<< " in " << task.dialog_list_id;
CHECK(dialog_ids.size() <= static_cast<size_t>(total_count));
CHECK(dialog_ids.size() <= static_cast<size_t>(task.limit));
if (dialog_ids.size() == static_cast<size_t>(min(total_count, task.limit)) ||
list.list_last_dialog_date_ == MAX_DIALOG_DATE || task.retry_count == 0) {
auto task_promise = std::move(task.promise);
get_dialogs_tasks_.erase(task_it);
if (!task_promise) {
dialog_ids.clear();
}
return task_promise.set_value(get_chats_object(total_count, dialog_ids));
}
// nor the limit, nor the end of the list were reached; wait for the promise
}
void MessagesManager::on_get_dialogs_from_list(int64 task_id, Result<Unit> &&result) {
auto task_it = get_dialogs_tasks_.find(task_id);
if (task_it == get_dialogs_tasks_.end()) {
// the task has already been completed
LOG(INFO) << "Chat list load task " << task_id << " has already been completed";
return;
}
auto &task = task_it->second;
if (result.is_error()) {
LOG(INFO) << "Chat list load task " << task_id << " failed with the error " << result.error();
auto task_promise = std::move(task.promise);
get_dialogs_tasks_.erase(task_it);
return task_promise.set_error(result.move_as_error());
}
auto list_ptr = get_dialog_list(task.dialog_list_id);
CHECK(list_ptr != nullptr);
auto &list = *list_ptr;
if (task.last_dialog_date == list.list_last_dialog_date_) {
// no new chats were loaded
task.retry_count--;
} else {
CHECK(task.last_dialog_date < list.list_last_dialog_date_);
task.last_dialog_date = list.list_last_dialog_date_;
task.retry_count = 5;
}
get_dialogs_from_list_impl(task_id);
}
vector<DialogId> MessagesManager::get_pinned_dialog_ids(DialogListId dialog_list_id) const {
CHECK(!td_->auth_manager_->is_bot());
if (dialog_list_id.is_filter()) {
const auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
if (filter == nullptr) {
return {};
}
return InputDialogId::get_dialog_ids(filter->pinned_dialog_ids);
}
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr || !list->are_pinned_dialogs_inited_) {
return {};
}
return transform(list->pinned_dialogs_, [](auto &pinned_dialog) { return pinned_dialog.get_dialog_id(); });
}
void MessagesManager::reload_pinned_dialogs(DialogListId dialog_list_id, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(!td_->auth_manager_->is_bot());
if (dialog_list_id.is_folder()) {
td_->create_handler<GetPinnedDialogsQuery>(std::move(promise))->send(dialog_list_id.get_folder_id());
} else if (dialog_list_id.is_filter()) {
schedule_dialog_filters_reload(0.0);
dialog_filter_reload_queries_.push_back(std::move(promise));
}
}
double MessagesManager::get_dialog_filters_cache_time() {
return DIALOG_FILTERS_CACHE_TIME * 0.0001 * Random::fast(9000, 11000);
}
void MessagesManager::schedule_dialog_filters_reload(double timeout) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (timeout <= 0) {
timeout = 0.0;
if (dialog_filters_updated_date_ != 0) {
dialog_filters_updated_date_ = 0;
save_dialog_filters();
}
}
LOG(INFO) << "Schedule reload of chat filters in " << timeout;
reload_dialog_filters_timeout_.set_callback(std::move(MessagesManager::on_reload_dialog_filters_timeout));
reload_dialog_filters_timeout_.set_callback_data(static_cast<void *>(this));
reload_dialog_filters_timeout_.set_timeout_in(timeout);
}
void MessagesManager::on_reload_dialog_filters_timeout(void *messages_manager_ptr) {
if (G()->close_flag()) {
return;
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::reload_dialog_filters);
}
void MessagesManager::reload_dialog_filters() {
if (G()->close_flag()) {
return;
}
CHECK(!td_->auth_manager_->is_bot());
if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
need_dialog_filters_reload_ = true;
return;
}
LOG(INFO) << "Reload chat filters from server";
are_dialog_filters_being_reloaded_ = true;
need_dialog_filters_reload_ = false;
auto promise = PromiseCreator::lambda(
[actor_id = actor_id(this)](Result<vector<tl_object_ptr<telegram_api::DialogFilter>>> r_filters) {
send_closure(actor_id, &MessagesManager::on_get_dialog_filters, std::move(r_filters), false);
});
td_->create_handler<GetDialogFiltersQuery>(std::move(promise))->send();
}
void MessagesManager::on_get_dialog_filters(Result<vector<tl_object_ptr<telegram_api::DialogFilter>>> r_filters,
bool dummy) {
if (G()->close_flag()) {
return;
}
are_dialog_filters_being_reloaded_ = false;
CHECK(!td_->auth_manager_->is_bot());
auto promises = std::move(dialog_filter_reload_queries_);
dialog_filter_reload_queries_.clear();
if (r_filters.is_error()) {
if (!G()->is_expected_error(r_filters.error())) {
LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery";
}
fail_promises(promises, r_filters.move_as_error());
need_dialog_filters_reload_ = false;
schedule_dialog_filters_reload(Random::fast(60, 5 * 60));
return;
}
auto filters = r_filters.move_as_ok();
vector<unique_ptr<DialogFilter>> new_server_dialog_filters;
LOG(INFO) << "Receive chat filters from server: " << to_string(filters);
std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids;
int32 server_main_dialog_list_position = -1;
int32 position = 0;
for (auto &filter : filters) {
if (filter->get_id() == telegram_api::dialogFilterDefault::ID) {
if (server_main_dialog_list_position == -1) {
server_main_dialog_list_position = position;
} else {
LOG(ERROR) << "Receive duplicate dialogFilterDefault";
}
continue;
}
auto dialog_filter = DialogFilter::get_dialog_filter(std::move(filter), true);
if (dialog_filter == nullptr) {
continue;
}
if (!new_dialog_filter_ids.insert(dialog_filter->dialog_filter_id).second) {
LOG(ERROR) << "Receive duplicate " << dialog_filter->dialog_filter_id;
continue;
}
sort_dialog_filter_input_dialog_ids(dialog_filter.get(), "on_get_dialog_filters 1");
new_server_dialog_filters.push_back(std::move(dialog_filter));
position++;
}
if (server_main_dialog_list_position == -1) {
LOG(ERROR) << "Receive no dialogFilterDefault";
server_main_dialog_list_position = 0;
}
if (server_main_dialog_list_position != 0 && !td_->option_manager_->get_option_boolean("is_premium")) {
LOG(INFO) << "Ignore server main chat list position " << server_main_dialog_list_position;
server_main_dialog_list_position = 0;
}
bool is_changed = false;
dialog_filters_updated_date_ = G()->unix_time();
if (server_dialog_filters_ != new_server_dialog_filters) {
LOG(INFO) << "Change server chat filters from "
<< get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_) << " to "
<< get_dialog_filter_ids(new_server_dialog_filters, server_main_dialog_list_position);
FlatHashMap<DialogFilterId, const DialogFilter *, DialogFilterIdHash> old_server_dialog_filters;
for (const auto &filter : server_dialog_filters_) {
old_server_dialog_filters.emplace(filter->dialog_filter_id, filter.get());
}
for (const auto &new_server_filter : new_server_dialog_filters) {
auto dialog_filter_id = new_server_filter->dialog_filter_id;
auto old_filter = get_dialog_filter(dialog_filter_id);
auto it = old_server_dialog_filters.find(dialog_filter_id);
if (it != old_server_dialog_filters.end()) {
auto old_server_filter = it->second;
if (*new_server_filter != *old_server_filter) {
if (old_filter == nullptr) {
// the filter was deleted, don't need to edit it
} else {
if (DialogFilter::are_equivalent(*old_filter, *new_server_filter)) { // fast path
// the filter was edited from this client, nothing to do
} else {
auto new_filter =
DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter, new_server_filter.get());
LOG(INFO) << "Old local filter: " << *old_filter;
LOG(INFO) << "Old server filter: " << *old_server_filter;
LOG(INFO) << "New server filter: " << *new_server_filter;
LOG(INFO) << "New local filter: " << *new_filter;
sort_dialog_filter_input_dialog_ids(new_filter.get(), "on_get_dialog_filters 2");
if (*new_filter != *old_filter) {
is_changed = true;
edit_dialog_filter(std::move(new_filter), "on_get_dialog_filters");
}
}
}
}
old_server_dialog_filters.erase(it);
} else {
if (old_filter == nullptr) {
// the filter was added from another client
is_changed = true;
add_dialog_filter(make_unique<DialogFilter>(*new_server_filter), false, "on_get_dialog_filters");
} else {
// the filter was added from this client
// after that it could be added from another client, or edited from this client, or edited from another client
// prefer local value, so do nothing
// effectively, ignore edits from other clients, if didn't receive UpdateDialogFilterQuery response
}
}
}
vector<DialogFilterId> left_old_server_dialog_filter_ids;
for (const auto &filter : server_dialog_filters_) {
if (old_server_dialog_filters.count(filter->dialog_filter_id) == 0) {
left_old_server_dialog_filter_ids.push_back(filter->dialog_filter_id);
}
}
LOG(INFO) << "Still existing server chat filters: " << left_old_server_dialog_filter_ids;
for (auto &old_server_filter : old_server_dialog_filters) {
auto dialog_filter_id = old_server_filter.first;
// deleted filter
auto old_filter = get_dialog_filter(dialog_filter_id);
if (old_filter == nullptr) {
// the filter was deleted from this client, nothing to do
} else {
// the filter was deleted from another client
// ignore edits done from the current client and just delete the filter
is_changed = true;
delete_dialog_filter(dialog_filter_id, "on_get_dialog_filters");
}
}
bool is_order_changed = [&] {
vector<DialogFilterId> new_server_dialog_filter_ids = get_dialog_filter_ids(new_server_dialog_filters, -1);
CHECK(new_server_dialog_filter_ids.size() >= left_old_server_dialog_filter_ids.size());
new_server_dialog_filter_ids.resize(left_old_server_dialog_filter_ids.size());
return new_server_dialog_filter_ids != left_old_server_dialog_filter_ids;
}();
if (is_order_changed) { // if order is changed from this and other clients, prefer order from another client
vector<DialogFilterId> new_dialog_filter_order;
for (const auto &new_server_filter : new_server_dialog_filters) {
auto dialog_filter_id = new_server_filter->dialog_filter_id;
if (get_dialog_filter(dialog_filter_id) != nullptr) {
new_dialog_filter_order.push_back(dialog_filter_id);
}
}
is_changed = true;
set_dialog_filters_order(dialog_filters_, new_dialog_filter_order);
}
server_dialog_filters_ = std::move(new_server_dialog_filters);
}
if (server_main_dialog_list_position_ != server_main_dialog_list_position) {
server_main_dialog_list_position_ = server_main_dialog_list_position;
int32 main_dialog_list_position = -1;
if (server_main_dialog_list_position == 0) {
main_dialog_list_position = 0;
} else {
int32 current_position = 0;
int32 current_server_position = 0;
for (const auto &dialog_filter : dialog_filters_) {
current_position++;
if (!dialog_filter->is_empty(true)) {
current_server_position++;
}
if (current_server_position == server_main_dialog_list_position) {
main_dialog_list_position = current_position;
}
}
if (main_dialog_list_position == -1) {
LOG(INFO) << "Failed to find server position " << server_main_dialog_list_position << " in chat filters";
main_dialog_list_position = static_cast<int32>(dialog_filters_.size());
}
}
if (main_dialog_list_position != main_dialog_list_position_) {
LOG(INFO) << "Change main chat list position from " << main_dialog_list_position_ << " to "
<< main_dialog_list_position;
main_dialog_list_position_ = main_dialog_list_position;
is_changed = true;
}
}
if (is_changed || !is_update_chat_filters_sent_) {
send_update_chat_filters();
}
schedule_dialog_filters_reload(get_dialog_filters_cache_time());
save_dialog_filters();
if (need_synchronize_dialog_filters()) {
synchronize_dialog_filters();
}
set_promises(promises);
}
bool MessagesManager::need_synchronize_dialog_filters() const {
CHECK(!td_->auth_manager_->is_bot());
size_t server_dialog_filter_count = 0;
vector<DialogFilterId> dialog_filter_ids;
for (const auto &dialog_filter : dialog_filters_) {
if (dialog_filter->is_empty(true)) {
continue;
}
server_dialog_filter_count++;
auto server_dialog_filter = get_server_dialog_filter(dialog_filter->dialog_filter_id);
if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
// need update dialog filter on server
return true;
}
dialog_filter_ids.push_back(dialog_filter->dialog_filter_id);
}
if (server_dialog_filter_count != server_dialog_filters_.size()) {
// need delete dialog filter on server
return true;
}
if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_, -1)) {
// need reorder dialog filters on server
return true;
}
if (get_server_main_dialog_list_position() != server_main_dialog_list_position_) {
// need reorder main chat list on server
return true;
}
return false;
}
void MessagesManager::synchronize_dialog_filters() {
if (G()->close_flag()) {
return;
}
CHECK(!td_->auth_manager_->is_bot());
if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
return;
}
if (need_dialog_filters_reload_) {
return reload_dialog_filters();
}
if (!need_synchronize_dialog_filters()) {
// reload filters to repair their order if the server added new filter to the beginning of the list
return reload_dialog_filters();
}
LOG(INFO) << "Synchronize chat filter changes with server having local "
<< get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_) << " and server "
<< get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_);
for (const auto &server_dialog_filter : server_dialog_filters_) {
if (get_dialog_filter(server_dialog_filter->dialog_filter_id) == nullptr) {
return delete_dialog_filter_on_server(server_dialog_filter->dialog_filter_id);
}
}
vector<DialogFilterId> dialog_filter_ids;
for (const auto &dialog_filter : dialog_filters_) {
if (dialog_filter->is_empty(true)) {
continue;
}
auto server_dialog_filter = get_server_dialog_filter(dialog_filter->dialog_filter_id);
if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
return update_dialog_filter_on_server(make_unique<DialogFilter>(*dialog_filter));
}
dialog_filter_ids.push_back(dialog_filter->dialog_filter_id);
}
auto server_main_dialog_list_position = get_server_main_dialog_list_position();
if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_, -1) ||
server_main_dialog_list_position != server_main_dialog_list_position_) {
return reorder_dialog_filters_on_server(std::move(dialog_filter_ids), server_main_dialog_list_position);
}
UNREACHABLE();
}
vector<DialogId> MessagesManager::search_public_dialogs(const string &query, Promise<Unit> &&promise) {
LOG(INFO) << "Search public chats with query = \"" << query << '"';
auto query_length = utf8_length(query);
if (query_length < MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN ||
(query_length == MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN && query[0] == '@')) {
string username = clean_username(query);
if (username[0] == '@') {
username = username.substr(1);
}
for (auto &short_username : get_valid_short_usernames()) {
if (2 * username.size() > short_username.size() && begins_with(short_username, username)) {
username = short_username.str();
auto resolved_username = resolved_usernames_.get(username);
if (!resolved_username.dialog_id.is_valid()) {
td_->create_handler<ResolveUsernameQuery>(std::move(promise))->send(username);
return {};
}
if (resolved_username.expires_at < Time::now()) {
td_->create_handler<ResolveUsernameQuery>(Promise<Unit>())->send(username);
}
auto dialog_id = resolved_username.dialog_id;
force_create_dialog(dialog_id, "public dialogs search");
auto d = get_dialog(dialog_id);
if (d == nullptr || d->order != DEFAULT_ORDER ||
(dialog_id.get_type() == DialogType::User &&
td_->contacts_manager_->is_user_contact(dialog_id.get_user_id()))) {
continue;
}
promise.set_value(Unit());
return {dialog_id};
}
}
promise.set_value(Unit());
return {};
}
auto it = found_public_dialogs_.find(query);
if (it != found_public_dialogs_.end()) {
promise.set_value(Unit());
return it->second;
}
send_search_public_dialogs_query(query, std::move(promise));
return {};
}
void MessagesManager::send_search_public_dialogs_query(const string &query, Promise<Unit> &&promise) {
CHECK(!query.empty());
auto &promises = search_public_dialogs_queries_[query];
promises.push_back(std::move(promise));
if (promises.size() != 1) {
// query has already been sent, just wait for the result
return;
}
td_->create_handler<SearchPublicDialogsQuery>()->send(query);
}
std::pair<int32, vector<DialogId>> MessagesManager::search_dialogs(const string &query, int32 limit,
Promise<Unit> &&promise) {
LOG(INFO) << "Search chats with query \"" << query << "\" and limit " << limit;
CHECK(!td_->auth_manager_->is_bot());
if (limit < 0) {
promise.set_error(Status::Error(400, "Limit must be non-negative"));
return {};
}
if (query.empty()) {
return recently_found_dialogs_.get_dialogs(limit, std::move(promise));
}
auto result = dialogs_hints_.search(query, limit);
vector<DialogId> dialog_ids;
dialog_ids.reserve(result.second.size());
for (auto key : result.second) {
dialog_ids.push_back(DialogId(-key));
}
promise.set_value(Unit());
return {narrow_cast<int32>(result.first), std::move(dialog_ids)};
}
std::pair<int32, vector<DialogId>> MessagesManager::get_recently_opened_dialogs(int32 limit, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
return recently_opened_dialogs_.get_dialogs(limit, std::move(promise));
}
vector<DialogId> MessagesManager::sort_dialogs_by_order(const vector<DialogId> &dialog_ids, int32 limit) const {
CHECK(!td_->auth_manager_->is_bot());
auto fake_order = static_cast<int64>(dialog_ids.size()) + 1;
auto dialog_dates = transform(dialog_ids, [this, &fake_order](DialogId dialog_id) {
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto order = get_dialog_base_order(d);
if (is_dialog_inited(d) || order != DEFAULT_ORDER) {
return DialogDate(order, dialog_id);
}
// if the dialog is not inited yet, we need to assume that server knows better and the dialog needs to be returned
return DialogDate(fake_order--, dialog_id);
});
if (static_cast<size_t>(limit) >= dialog_dates.size()) {
std::sort(dialog_dates.begin(), dialog_dates.end());
} else {
std::partial_sort(dialog_dates.begin(), dialog_dates.begin() + limit, dialog_dates.end());
dialog_dates.resize(limit, MAX_DIALOG_DATE);
}
while (!dialog_dates.empty() && dialog_dates.back().get_order() == DEFAULT_ORDER) {
dialog_dates.pop_back();
}
return transform(dialog_dates, [](auto dialog_date) { return dialog_date.get_dialog_id(); });
}
vector<DialogId> MessagesManager::search_dialogs_on_server(const string &query, int32 limit, Promise<Unit> &&promise) {
LOG(INFO) << "Search chats on server with query \"" << query << "\" and limit " << limit;
if (limit < 0) {
promise.set_error(Status::Error(400, "Limit must be non-negative"));
return {};
}
if (limit > MAX_GET_DIALOGS) {
limit = MAX_GET_DIALOGS;
}
if (query.empty()) {
promise.set_value(Unit());
return {};
}
auto it = found_on_server_dialogs_.find(query);
if (it != found_on_server_dialogs_.end()) {
promise.set_value(Unit());
return sort_dialogs_by_order(it->second, limit);
}
send_search_public_dialogs_query(query, std::move(promise));
return {};
}
void MessagesManager::drop_common_dialogs_cache(UserId user_id) {
auto it = found_common_dialogs_.find(user_id);
if (it != found_common_dialogs_.end()) {
it->second.is_outdated = true;
}
}
std::pair<int32, vector<DialogId>> MessagesManager::get_common_dialogs(UserId user_id, DialogId offset_dialog_id,
int32 limit, bool force,
Promise<Unit> &&promise) {
auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
if (r_input_user.is_error()) {
promise.set_error(r_input_user.move_as_error());
return {};
}
if (user_id == td_->contacts_manager_->get_my_id()) {
promise.set_error(Status::Error(400, "Can't get common chats with self"));
return {};
}
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
if (limit > MAX_GET_DIALOGS) {
limit = MAX_GET_DIALOGS;
}
int64 offset_chat_id = 0;
switch (offset_dialog_id.get_type()) {
case DialogType::Chat:
offset_chat_id = offset_dialog_id.get_chat_id().get();
break;
case DialogType::Channel:
offset_chat_id = offset_dialog_id.get_channel_id().get();
break;
case DialogType::None:
if (offset_dialog_id == DialogId()) {
break;
}
// fallthrough
case DialogType::User:
case DialogType::SecretChat:
promise.set_error(Status::Error(400, "Wrong offset_chat_id"));
return {};
default:
UNREACHABLE();
break;
}
auto it = found_common_dialogs_.find(user_id);
if (it != found_common_dialogs_.end() && !it->second.dialog_ids.empty()) {
int32 total_count = it->second.total_count;
vector<DialogId> &common_dialog_ids = it->second.dialog_ids;
bool use_cache = (!it->second.is_outdated && it->second.receive_time >= Time::now() - 3600) || force ||
offset_chat_id != 0 || common_dialog_ids.size() >= static_cast<size_t>(MAX_GET_DIALOGS);
// use cache if it is up-to-date, or we required to use it or we can't update it
if (use_cache) {
auto offset_it = common_dialog_ids.begin();
if (offset_dialog_id != DialogId()) {
offset_it = std::find(common_dialog_ids.begin(), common_dialog_ids.end(), offset_dialog_id);
if (offset_it == common_dialog_ids.end()) {
promise.set_error(Status::Error(400, "Wrong offset_chat_id"));
return {};
}
++offset_it;
}
vector<DialogId> result;
while (result.size() < static_cast<size_t>(limit)) {
if (offset_it == common_dialog_ids.end()) {
break;
}
auto dialog_id = *offset_it++;
if (dialog_id == DialogId()) { // end of the list
promise.set_value(Unit());
return {total_count, std::move(result)};
}
result.push_back(dialog_id);
}
if (result.size() == static_cast<size_t>(limit) || force) {
promise.set_value(Unit());
return {total_count, std::move(result)};
}
}
}
td_->create_handler<GetCommonDialogsQuery>(std::move(promise))
->send(user_id, r_input_user.move_as_ok(), offset_chat_id, MAX_GET_DIALOGS);
return {};
}
void MessagesManager::on_get_common_dialogs(UserId user_id, int64 offset_chat_id,
vector<tl_object_ptr<telegram_api::Chat>> &&chats, int32 total_count) {
CHECK(user_id.is_valid());
td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count);
auto &common_dialogs = found_common_dialogs_[user_id];
if (common_dialogs.is_outdated && offset_chat_id == 0 &&
common_dialogs.dialog_ids.size() < static_cast<size_t>(MAX_GET_DIALOGS)) {
// drop outdated cache if possible
common_dialogs = CommonDialogs();
}
if (common_dialogs.receive_time == 0) {
common_dialogs.receive_time = Time::now();
}
common_dialogs.is_outdated = false;
auto &result = common_dialogs.dialog_ids;
if (!result.empty() && result.back() == DialogId()) {
return;
}
bool is_last = chats.empty() && offset_chat_id == 0;
for (auto &chat : chats) {
auto dialog_id = ContactsManager::get_dialog_id(chat);
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << to_string(chat);
continue;
}
td_->contacts_manager_->on_get_chat(std::move(chat), "on_get_common_dialogs");
if (!td::contains(result, dialog_id)) {
force_create_dialog(dialog_id, "get common dialogs");
result.push_back(dialog_id);
}
}
if (result.size() >= static_cast<size_t>(total_count) || is_last) {
if (result.size() != static_cast<size_t>(total_count)) {
LOG(ERROR) << "Fix total count of common groups with " << user_id << " from " << total_count << " to "
<< result.size();
total_count = narrow_cast<int32>(result.size());
td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count);
}
result.emplace_back();
}
common_dialogs.total_count = total_count;
}
void MessagesManager::block_message_sender_from_replies(MessageId message_id, bool need_delete_message,
bool need_delete_all_messages, bool report_spam,
Promise<Unit> &&promise) {
auto dialog_id = DialogId(ContactsManager::get_replies_bot_user_id());
Dialog *d = get_dialog_force(dialog_id, "block_message_sender_from_replies");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Not enough rights"));
}
auto *m = get_message_force(d, message_id, "block_message_sender_from_replies");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (m->is_outgoing || m->message_id.is_scheduled() || !m->message_id.is_server()) {
return promise.set_error(Status::Error(400, "Wrong message specified"));
}
UserId sender_user_id;
if (m->forward_info != nullptr) {
sender_user_id = m->forward_info->sender_user_id;
}
vector<MessageId> message_ids;
if (need_delete_all_messages && sender_user_id.is_valid()) {
find_messages(d->messages.get(), message_ids, [sender_user_id](const Message *m) {
return !m->is_outgoing && m->forward_info != nullptr && m->forward_info->sender_user_id == sender_user_id;
});
CHECK(td::contains(message_ids, message_id));
} else if (need_delete_message) {
message_ids.push_back(message_id);
}
delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
block_message_sender_from_replies_on_server(message_id, need_delete_message, need_delete_all_messages, report_spam, 0,
std::move(promise));
}
class MessagesManager::BlockMessageSenderFromRepliesOnServerLogEvent {
public:
MessageId message_id_;
bool delete_message_;
bool delete_all_messages_;
bool report_spam_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(delete_message_);
STORE_FLAG(delete_all_messages_);
STORE_FLAG(report_spam_);
END_STORE_FLAGS();
td::store(message_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(delete_message_);
PARSE_FLAG(delete_all_messages_);
PARSE_FLAG(report_spam_);
END_PARSE_FLAGS();
td::parse(message_id_, parser);
}
};
uint64 MessagesManager::save_block_message_sender_from_replies_on_server_log_event(MessageId message_id,
bool need_delete_message,
bool need_delete_all_messages,
bool report_spam) {
BlockMessageSenderFromRepliesOnServerLogEvent log_event{message_id, need_delete_message, need_delete_all_messages,
report_spam};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::block_message_sender_from_replies_on_server(MessageId message_id, bool need_delete_message,
bool need_delete_all_messages, bool report_spam,
uint64 log_event_id, Promise<Unit> &&promise) {
if (log_event_id == 0) {
log_event_id = save_block_message_sender_from_replies_on_server_log_event(message_id, need_delete_message,
need_delete_all_messages, report_spam);
}
td_->create_handler<BlockFromRepliesQuery>(get_erase_log_event_promise(log_event_id, std::move(promise)))
->send(message_id, need_delete_message, need_delete_all_messages, report_spam);
}
void MessagesManager::get_blocked_dialogs(int32 offset, int32 limit,
Promise<td_api::object_ptr<td_api::messageSenders>> &&promise) {
if (offset < 0) {
return promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
}
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
td_->create_handler<GetBlockedDialogsQuery>(std::move(promise))->send(offset, limit);
}
void MessagesManager::on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count,
vector<tl_object_ptr<telegram_api::peerBlocked>> &&blocked_peers,
Promise<td_api::object_ptr<td_api::messageSenders>> &&promise) {
LOG(INFO) << "Receive " << blocked_peers.size() << " blocked chats from offset " << offset << " out of "
<< total_count;
auto peers = transform(std::move(blocked_peers), [](tl_object_ptr<telegram_api::peerBlocked> &&blocked_peer) {
return std::move(blocked_peer->peer_id_);
});
auto dialog_ids = get_message_sender_dialog_ids(td_, std::move(peers));
if (!dialog_ids.empty() && offset + dialog_ids.size() > static_cast<size_t>(total_count)) {
LOG(ERROR) << "Fix total count of blocked chats from " << total_count << " to " << offset + dialog_ids.size();
total_count = offset + narrow_cast<int32>(dialog_ids.size());
}
auto senders = transform(dialog_ids, [td = td_](DialogId dialog_id) {
return get_message_sender_object(td, dialog_id, "on_get_blocked_dialogs");
});
promise.set_value(td_api::make_object<td_api::messageSenders>(total_count, std::move(senders)));
}
DialogId MessagesManager::get_dialog_message_sender(FullMessageId full_message_id) {
const auto *m = get_message_force(full_message_id, "get_dialog_message_sender");
if (m == nullptr) {
return DialogId();
}
return get_message_sender(m);
}
bool MessagesManager::have_message_force(FullMessageId full_message_id, const char *source) {
return get_message_force(full_message_id, source) != nullptr;
}
bool MessagesManager::have_message_force(Dialog *d, MessageId message_id, const char *source) {
return get_message_force(d, message_id, source) != nullptr;
}
MessagesManager::Message *MessagesManager::get_message(FullMessageId full_message_id) {
Dialog *d = get_dialog(full_message_id.get_dialog_id());
if (d == nullptr) {
return nullptr;
}
return get_message(d, full_message_id.get_message_id());
}
const MessagesManager::Message *MessagesManager::get_message(FullMessageId full_message_id) const {
const Dialog *d = get_dialog(full_message_id.get_dialog_id());
if (d == nullptr) {
return nullptr;
}
return get_message(d, full_message_id.get_message_id());
}
MessagesManager::Message *MessagesManager::get_message_force(FullMessageId full_message_id, const char *source) {
Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), source);
if (d == nullptr) {
return nullptr;
}
return get_message_force(d, full_message_id.get_message_id(), source);
}
FullMessageId MessagesManager::get_replied_message_id(DialogId dialog_id, const Message *m) {
auto full_message_id = get_message_content_replied_message_id(dialog_id, m->content.get());
if (full_message_id.get_message_id().is_valid()) {
CHECK(m->reply_to_message_id == MessageId());
return full_message_id;
}
if (m->reply_to_message_id == MessageId()) {
if (m->top_thread_message_id.is_valid() && is_service_message_content(m->content->get_type())) {
return {dialog_id, m->top_thread_message_id};
}
return {};
}
return {m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, m->reply_to_message_id};
}
void MessagesManager::get_message_force_from_server(Dialog *d, MessageId message_id, Promise<Unit> &&promise,
tl_object_ptr<telegram_api::InputMessage> input_message) {
LOG(INFO) << "Get " << message_id << " in " << d->dialog_id << " using " << to_string(input_message);
auto dialog_type = d->dialog_id.get_type();
auto m = get_message_force(d, message_id, "get_message_force_from_server");
if (m == nullptr && !is_deleted_message(d, message_id) && dialog_type != DialogType::SecretChat) {
if (message_id.is_valid() && message_id.is_server()) {
if (d->last_new_message_id != MessageId() && message_id > d->last_new_message_id &&
dialog_type != DialogType::Channel) {
// message will not be added to the dialog anyway
return promise.set_value(Unit());
}
return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server",
std::move(input_message));
}
if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && input_message == nullptr) {
return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server");
}
}
promise.set_value(Unit());
}
void MessagesManager::get_message(FullMessageId full_message_id, Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), "get_message");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
get_message_force_from_server(d, full_message_id.get_message_id(), std::move(promise));
}
FullMessageId MessagesManager::get_replied_message(DialogId dialog_id, MessageId message_id, bool force,
Promise<Unit> &&promise) {
LOG(INFO) << "Get replied message to " << message_id << " in " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "get_replied_message");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return FullMessageId();
}
message_id = get_persistent_message_id(d, message_id);
auto m = get_message_force(d, message_id, "get_replied_message");
if (m == nullptr) {
if (force) {
promise.set_value(Unit());
} else {
get_message_force_from_server(d, message_id, std::move(promise));
}
return FullMessageId();
}
tl_object_ptr<telegram_api::InputMessage> input_message;
auto replied_message_id = get_replied_message_id(dialog_id, m);
if (replied_message_id.get_dialog_id() != dialog_id) {
dialog_id = replied_message_id.get_dialog_id();
if (!have_dialog_info_force(dialog_id)) {
promise.set_value(Unit());
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_value(Unit());
return {};
}
force_create_dialog(dialog_id, "get_replied_message");
d = get_dialog_force(dialog_id, "get_replied_message");
if (d == nullptr) {
promise.set_error(Status::Error(500, "Chat with replied message not found"));
return {};
}
} else if (m->message_id.is_valid() && m->message_id.is_server()) {
input_message = make_tl_object<telegram_api::inputMessageReplyTo>(m->message_id.get_server_message_id().get());
}
get_message_force_from_server(d, replied_message_id.get_message_id(), std::move(promise), std::move(input_message));
return replied_message_id;
}
Result<FullMessageId> MessagesManager::get_top_thread_full_message_id(DialogId dialog_id, const Message *m,
bool allow_non_root) const {
CHECK(m != nullptr);
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Message is scheduled");
}
if (dialog_id.get_type() != DialogType::Channel) {
return Status::Error(400, "Chat can't have message threads");
}
if (!m->reply_info.is_empty() && m->reply_info.is_comment_) {
if (!is_visible_message_reply_info(dialog_id, m)) {
return Status::Error(400, "Message has no comments");
}
if (m->message_id.is_yet_unsent()) {
return Status::Error(400, "Message is not sent yet");
}
return FullMessageId{DialogId(m->reply_info.channel_id_), m->linked_top_thread_message_id};
} else {
if (!m->top_thread_message_id.is_valid()) {
return Status::Error(400, "Message has no thread");
}
if (!allow_non_root && m->top_thread_message_id != m->message_id &&
!td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) {
return Status::Error(400, "Root message must be used to get the message thread");
}
return FullMessageId{dialog_id, m->top_thread_message_id};
}
}
void MessagesManager::get_message_thread(DialogId dialog_id, MessageId message_id,
Promise<MessageThreadInfo> &&promise) {
LOG(INFO) << "Get message thread from " << message_id << " in " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "get_message_thread");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (dialog_id.get_type() != DialogType::Channel) {
return promise.set_error(Status::Error(400, "Chat is not a supergroup or a channel"));
}
if (message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Scheduled messages can't have message threads"));
}
message_id = get_persistent_message_id(d, message_id);
auto m = get_message_force(d, message_id, "get_message_thread");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
TRY_RESULT_PROMISE(promise, top_thread_full_message_id, get_top_thread_full_message_id(dialog_id, m, true));
if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) &&
top_thread_full_message_id.get_message_id() != m->message_id) {
CHECK(dialog_id == top_thread_full_message_id.get_dialog_id());
// get information about the thread from the top message
message_id = top_thread_full_message_id.get_message_id();
CHECK(message_id.is_valid());
}
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_id,
promise = std::move(promise)](Result<MessageThreadInfo> result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
send_closure(actor_id, &MessagesManager::on_get_discussion_message, dialog_id, message_id, result.move_as_ok(),
std::move(promise));
});
td_->create_handler<GetDiscussionMessageQuery>(std::move(query_promise))
->send(dialog_id, message_id, top_thread_full_message_id.get_dialog_id(),
top_thread_full_message_id.get_message_id());
}
void MessagesManager::process_discussion_message(
telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, DialogId dialog_id,
MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id,
Promise<MessageThreadInfo> promise) {
LOG(INFO) << "Receive discussion message for " << message_id << " in " << dialog_id << ": " << to_string(result);
td_->contacts_manager_->on_get_users(std::move(result->users_), "process_discussion_message");
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message");
for (auto &message : result->messages_) {
auto message_dialog_id = DialogId::get_message_dialog_id(message);
if (message_dialog_id != expected_dialog_id) {
return promise.set_error(Status::Error(500, "Expected messages in a different chat"));
}
}
for (auto &message : result->messages_) {
if (need_channel_difference_to_add_message(expected_dialog_id, message)) {
return run_after_channel_difference(
expected_dialog_id, PromiseCreator::lambda([actor_id = actor_id(this), result = std::move(result), dialog_id,
message_id, expected_dialog_id, expected_message_id,
promise = std::move(promise)](Unit ignored) mutable {
send_closure(actor_id, &MessagesManager::process_discussion_message_impl, std::move(result), dialog_id,
message_id, expected_dialog_id, expected_message_id, std::move(promise));
}));
}
}
process_discussion_message_impl(std::move(result), dialog_id, message_id, expected_dialog_id, expected_message_id,
std::move(promise));
}
void MessagesManager::process_discussion_message_impl(
telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, DialogId dialog_id,
MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id,
Promise<MessageThreadInfo> promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
MessageThreadInfo message_thread_info;
message_thread_info.dialog_id = expected_dialog_id;
message_thread_info.unread_message_count = max(0, result->unread_count_);
MessageId top_message_id;
for (auto &message : result->messages_) {
auto full_message_id =
on_get_message(std::move(message), false, true, false, false, false, "process_discussion_message_impl");
if (full_message_id.get_message_id().is_valid()) {
CHECK(full_message_id.get_dialog_id() == expected_dialog_id);
message_thread_info.message_ids.push_back(full_message_id.get_message_id());
if (full_message_id.get_message_id() == expected_message_id) {
top_message_id = expected_message_id;
}
}
}
if (!message_thread_info.message_ids.empty() && !top_message_id.is_valid()) {
top_message_id = message_thread_info.message_ids.back();
}
auto max_message_id = MessageId(ServerMessageId(result->max_id_));
auto last_read_inbox_message_id = MessageId(ServerMessageId(result->read_inbox_max_id_));
auto last_read_outbox_message_id = MessageId(ServerMessageId(result->read_outbox_max_id_));
if (top_message_id.is_valid()) {
on_update_read_message_comments(expected_dialog_id, top_message_id, max_message_id, last_read_inbox_message_id,
last_read_outbox_message_id, message_thread_info.unread_message_count);
}
if (expected_dialog_id != dialog_id) {
on_update_read_message_comments(dialog_id, message_id, max_message_id, last_read_inbox_message_id,
last_read_outbox_message_id, message_thread_info.unread_message_count);
}
promise.set_value(std::move(message_thread_info));
}
void MessagesManager::on_get_discussion_message(DialogId dialog_id, MessageId message_id,
MessageThreadInfo &&message_thread_info,
Promise<MessageThreadInfo> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
Dialog *d = get_dialog_force(dialog_id, "on_get_discussion_message");
CHECK(d != nullptr);
CHECK(message_id.is_valid());
auto m = get_message_force(d, message_id, "on_get_discussion_message");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (message_thread_info.message_ids.empty()) {
if (message_thread_info.dialog_id != dialog_id &&
!have_input_peer(message_thread_info.dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access message comments"));
}
return promise.set_error(Status::Error(400, "Message has no thread"));
}
DialogId expected_dialog_id;
if (m->reply_info.is_comment_) {
if (!is_active_message_reply_info(dialog_id, m->reply_info)) {
return promise.set_error(Status::Error(400, "Message has no comments"));
}
expected_dialog_id = DialogId(m->reply_info.channel_id_);
} else {
if (!m->top_thread_message_id.is_valid()) {
return promise.set_error(Status::Error(400, "Message has no thread"));
}
expected_dialog_id = dialog_id;
}
if (expected_dialog_id != dialog_id && m->reply_info.is_comment_ &&
m->linked_top_thread_message_id != message_thread_info.message_ids.back()) {
auto linked_d = get_dialog_force(expected_dialog_id, "on_get_discussion_message 2");
CHECK(linked_d != nullptr);
auto linked_message_id = message_thread_info.message_ids.back();
Message *linked_m = get_message_force(linked_d, linked_message_id, "on_get_discussion_message 3");
CHECK(linked_m != nullptr && linked_m->message_id.is_server());
if (linked_m->top_thread_message_id == linked_m->message_id &&
is_active_message_reply_info(expected_dialog_id, linked_m->reply_info)) {
if (m->linked_top_thread_message_id.is_valid()) {
LOG(ERROR) << "Comment message identifier for " << message_id << " in " << dialog_id << " changed from "
<< m->linked_top_thread_message_id << " to " << linked_message_id;
}
m->linked_top_thread_message_id = linked_message_id;
on_dialog_updated(dialog_id, "on_get_discussion_message");
}
}
promise.set_value(std::move(message_thread_info));
}
td_api::object_ptr<td_api::messageThreadInfo> MessagesManager::get_message_thread_info_object(
const MessageThreadInfo &info) {
if (info.message_ids.empty()) {
return nullptr;
}
Dialog *d = get_dialog(info.dialog_id);
CHECK(d != nullptr);
td_api::object_ptr<td_api::messageReplyInfo> reply_info;
vector<td_api::object_ptr<td_api::message>> messages;
messages.reserve(info.message_ids.size());
bool is_forum_topic = false;
for (auto message_id : info.message_ids) {
const Message *m = get_message_force(d, message_id, "get_message_thread_info_object");
auto message = get_message_object(d->dialog_id, m, "get_message_thread_info_object");
if (message != nullptr) {
if (message->interaction_info_ != nullptr && message->interaction_info_->reply_info_ != nullptr) {
reply_info = m->reply_info.get_message_reply_info_object(td_, d->last_read_inbox_message_id);
CHECK(reply_info != nullptr);
}
is_forum_topic = message->is_topic_message_;
messages.push_back(std::move(message));
}
}
if (messages.size() != 1) {
is_forum_topic = false;
}
if (reply_info == nullptr && !is_forum_topic) {
return nullptr;
}
MessageId top_thread_message_id;
td_api::object_ptr<td_api::draftMessage> draft_message;
if (!info.message_ids.empty()) {
top_thread_message_id = info.message_ids.back();
if (can_send_message(d->dialog_id).is_ok()) {
const Message *m = get_message_force(d, top_thread_message_id, "get_message_thread_info_object 2");
if (m != nullptr && !m->reply_info.is_comment_ && is_active_message_reply_info(d->dialog_id, m->reply_info)) {
draft_message = get_draft_message_object(m->thread_draft_message);
}
}
}
return td_api::make_object<td_api::messageThreadInfo>(d->dialog_id.get(), top_thread_message_id.get(),
std::move(reply_info), info.unread_message_count,
std::move(messages), std::move(draft_message));
}
Status MessagesManager::can_get_message_viewers(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "get_message_viewers");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
auto m = get_message_force(d, full_message_id.get_message_id(), "get_message_viewers");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
return can_get_message_viewers(dialog_id, m);
}
Status MessagesManager::can_get_message_viewers(DialogId dialog_id, const Message *m) const {
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "User is bot");
}
CHECK(m != nullptr);
if (!m->is_outgoing) {
return Status::Error(400, "Can't get viewers of incoming messages");
}
if (G()->unix_time() - m->date >
td_->option_manager_->get_option_integer("chat_read_mark_expire_period", 7 * 86400)) {
return Status::Error(400, "Message is too old");
}
int32 participant_count = 0;
switch (dialog_id.get_type()) {
case DialogType::User:
return Status::Error(400, "Can't get message viewers in private chats");
case DialogType::Chat:
if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) {
return Status::Error(400, "Chat is deactivated");
}
participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id());
break;
case DialogType::Channel:
if (is_broadcast_channel(dialog_id)) {
return Status::Error(400, "Can't get message viewers in channel chats");
}
if (td_->contacts_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id())) {
return Status::Error(400, "Participant list is hidden in the chat");
}
participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
break;
case DialogType::SecretChat:
return Status::Error(400, "Can't get message viewers in secret chats");
case DialogType::None:
default:
UNREACHABLE();
return Status::OK();
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (participant_count == 0) {
return Status::Error(400, "Chat is empty or have unknown number of members");
}
if (participant_count > td_->option_manager_->get_option_integer("chat_read_mark_size_threshold", 100)) {
return Status::Error(400, "Chat is too big");
}
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Scheduled messages can't have viewers");
}
if (m->message_id.is_yet_unsent()) {
return Status::Error(400, "Yet unsent messages can't have viewers");
}
if (m->message_id.is_local()) {
return Status::Error(400, "Local messages can't have viewers");
}
CHECK(m->message_id.is_server());
if (m->content->get_type() == MessageContentType::Poll &&
get_message_content_poll_is_anonymous(td_, m->content.get())) {
return Status::Error(400, "Anonymous poll viewers are unavailable");
}
return Status::OK();
}
void MessagesManager::get_message_viewers(FullMessageId full_message_id,
Promise<td_api::object_ptr<td_api::users>> &&promise) {
TRY_STATUS_PROMISE(promise, can_get_message_viewers(full_message_id));
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = full_message_id.get_dialog_id(),
promise = std::move(promise)](Result<vector<UserId>> result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
send_closure(actor_id, &MessagesManager::on_get_message_viewers, dialog_id, result.move_as_ok(), false,
std::move(promise));
});
td_->create_handler<GetMessageReadParticipantsQuery>(std::move(query_promise))
->send(full_message_id.get_dialog_id(), full_message_id.get_message_id());
}
void MessagesManager::on_get_message_viewers(DialogId dialog_id, vector<UserId> user_ids, bool is_recursive,
Promise<td_api::object_ptr<td_api::users>> &&promise) {
if (!is_recursive) {
bool need_participant_list = false;
for (auto user_id : user_ids) {
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id << " as viewer of a message in " << dialog_id;
continue;
}
if (!td_->contacts_manager_->have_user_force(user_id)) {
need_participant_list = true;
}
}
if (need_participant_list) {
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, user_ids = std::move(user_ids),
promise = std::move(promise)](Unit result) mutable {
send_closure(actor_id, &MessagesManager::on_get_message_viewers, dialog_id, std::move(user_ids), true,
std::move(promise));
});
switch (dialog_id.get_type()) {
case DialogType::Chat:
return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise));
case DialogType::Channel:
return td_->contacts_manager_->get_channel_participants(
dialog_id.get_channel_id(), td_api::make_object<td_api::supergroupMembersFilterRecent>(), string(), 0,
200, 200, PromiseCreator::lambda([query_promise = std::move(query_promise)](DialogParticipants) mutable {
query_promise.set_value(Unit());
}));
default:
UNREACHABLE();
return;
}
}
}
promise.set_value(td_->contacts_manager_->get_users_object(-1, user_ids));
}
void MessagesManager::translate_text(const string &text, const string &from_language_code,
const string &to_language_code,
Promise<td_api::object_ptr<td_api::text>> &&promise) {
td_->create_handler<TranslateTextQuery>(std::move(promise))->send(text, from_language_code, to_language_code);
}
void MessagesManager::recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "recognize_speech");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
auto message_id = full_message_id.get_message_id();
if (message_id.is_scheduled() || !message_id.is_server()) {
return promise.set_error(Status::Error(400, "Message must be sent already"));
}
recognize_message_content_speech(td_, m->content.get(), full_message_id, std::move(promise));
}
void MessagesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "rate_speech_recognition");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
rate_message_content_speech_recognition(td_, m->content.get(), full_message_id, is_good, std::move(promise));
}
void MessagesManager::get_dialog_info_full(DialogId dialog_id, Promise<Unit> &&promise, const char *source) {
switch (dialog_id.get_type()) {
case DialogType::User:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_user_full, dialog_id.get_user_id(), false,
std::move(promise), source);
return;
case DialogType::Chat:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_chat_full, dialog_id.get_chat_id(), false,
std::move(promise), source);
return;
case DialogType::Channel:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_channel_full, dialog_id.get_channel_id(),
false, std::move(promise), source);
return;
case DialogType::SecretChat:
return promise.set_value(Unit());
case DialogType::None:
default:
UNREACHABLE();
return promise.set_error(Status::Error(500, "Wrong chat type"));
}
}
void MessagesManager::reload_dialog_info_full(DialogId dialog_id, const char *source) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Reload full info about " << dialog_id << " from " << source;
switch (dialog_id.get_type()) {
case DialogType::User:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_user_full, dialog_id.get_user_id(),
Promise<Unit>());
return;
case DialogType::Chat:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_chat_full, dialog_id.get_chat_id(),
Promise<Unit>());
return;
case DialogType::Channel:
send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_channel_full,
dialog_id.get_channel_id(), Promise<Unit>(), source);
return;
case DialogType::SecretChat:
return;
case DialogType::None:
default:
UNREACHABLE();
return;
}
}
void MessagesManager::on_dialog_info_full_invalidated(DialogId dialog_id) {
Dialog *d = get_dialog(dialog_id);
if (d != nullptr && d->is_opened) {
reload_dialog_info_full(dialog_id, "on_dialog_info_full_invalidated");
}
}
MessageId MessagesManager::get_dialog_pinned_message(DialogId dialog_id, Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "get_dialog_pinned_message");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return MessageId();
}
LOG(INFO) << "Get pinned message in " << dialog_id << " with "
<< (d->is_last_pinned_message_id_inited ? "inited" : "unknown") << " pinned " << d->last_pinned_message_id;
if (!d->is_last_pinned_message_id_inited) {
// must call get_dialog_info_full as expected in fix_new_dialog
get_dialog_info_full(dialog_id, std::move(promise), "get_dialog_pinned_message 1");
return MessageId();
}
get_dialog_info_full(dialog_id, Auto(), "get_dialog_pinned_message 2");
if (d->last_pinned_message_id.is_valid()) {
tl_object_ptr<telegram_api::InputMessage> input_message;
if (dialog_id.get_type() == DialogType::Channel) {
input_message = make_tl_object<telegram_api::inputMessagePinned>();
}
get_message_force_from_server(d, d->last_pinned_message_id, std::move(promise), std::move(input_message));
} else {
promise.set_value(Unit());
}
return d->last_pinned_message_id;
}
void MessagesManager::get_callback_query_message(DialogId dialog_id, MessageId message_id, int64 callback_query_id,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "get_callback_query_message");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!message_id.is_valid() || !message_id.is_server()) {
return promise.set_error(Status::Error(400, "Invalid message identifier specified"));
}
LOG(INFO) << "Get callback query " << message_id << " in " << dialog_id << " for query " << callback_query_id;
auto input_message = make_tl_object<telegram_api::inputMessageCallbackQuery>(message_id.get_server_message_id().get(),
callback_query_id);
get_message_force_from_server(d, message_id, std::move(promise), std::move(input_message));
}
bool MessagesManager::get_messages(DialogId dialog_id, const vector<MessageId> &message_ids, Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "get_messages");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return false;
}
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
vector<FullMessageId> missed_message_ids;
for (auto message_id : message_ids) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
promise.set_error(Status::Error(400, "Invalid message identifier"));
return false;
}
auto *m = get_message_force(d, message_id, "get_messages");
if (m == nullptr && message_id.is_any_server() && !is_secret) {
missed_message_ids.emplace_back(dialog_id, message_id);
continue;
}
}
if (!missed_message_ids.empty()) {
get_messages_from_server(std::move(missed_message_ids), std::move(promise), "get_messages");
return false;
}
promise.set_value(Unit());
return true;
}
void MessagesManager::get_message_from_server(FullMessageId full_message_id, Promise<Unit> &&promise,
const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message) {
get_messages_from_server({full_message_id}, std::move(promise), source, std::move(input_message));
}
void MessagesManager::get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise,
const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message) {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (message_ids.empty()) {
LOG(ERROR) << "Empty message_ids from " << source;
return promise.set_error(Status::Error(500, "There are no messages specified to fetch"));
}
if (input_message != nullptr) {
CHECK(message_ids.size() == 1);
}
vector<tl_object_ptr<telegram_api::InputMessage>> ordinary_message_ids;
FlatHashMap<ChannelId, vector<tl_object_ptr<telegram_api::InputMessage>>, ChannelIdHash> channel_message_ids;
FlatHashMap<DialogId, vector<int32>, DialogIdHash> scheduled_message_ids;
for (auto &full_message_id : message_ids) {
auto dialog_id = full_message_id.get_dialog_id();
auto message_id = full_message_id.get_message_id();
if (!message_id.is_valid() || !message_id.is_server()) {
if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && dialog_id.is_valid()) {
scheduled_message_ids[dialog_id].push_back(message_id.get_scheduled_server_message_id().get());
}
continue;
}
if (input_message == nullptr) {
input_message = make_tl_object<telegram_api::inputMessageID>(message_id.get_server_message_id().get());
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
ordinary_message_ids.push_back(std::move(input_message));
break;
case DialogType::Channel:
channel_message_ids[dialog_id.get_channel_id()].push_back(std::move(input_message));
break;
case DialogType::SecretChat:
LOG(ERROR) << "Can't get " << full_message_id << " from server from " << source;
break;
case DialogType::None:
default:
UNREACHABLE();
break;
}
}
MultiPromiseActorSafe mpas{"GetMessagesOnServerMultiPromiseActor"};
mpas.add_promise(std::move(promise));
auto lock = mpas.get_promise();
if (!ordinary_message_ids.empty()) {
td_->create_handler<GetMessagesQuery>(mpas.get_promise())->send(std::move(ordinary_message_ids));
}
for (auto &it : scheduled_message_ids) {
auto dialog_id = it.first;
have_dialog_force(dialog_id, "get_messages_from_server");
auto input_peer = get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
LOG(ERROR) << "Can't find info about " << dialog_id << " to get a message from it from " << source;
mpas.get_promise().set_error(Status::Error(400, "Can't access the chat"));
continue;
}
td_->create_handler<GetScheduledMessagesQuery>(mpas.get_promise())
->send(dialog_id, std::move(input_peer), std::move(it.second));
}
for (auto &it : channel_message_ids) {
td_->contacts_manager_->have_channel_force(it.first);
auto input_channel = td_->contacts_manager_->get_input_channel(it.first);
if (input_channel == nullptr) {
LOG(ERROR) << "Can't find info about " << it.first << " to get a message from it from " << source;
mpas.get_promise().set_error(Status::Error(400, "Can't access the chat"));
continue;
}
const auto *d = get_dialog_force(DialogId(it.first));
td_->create_handler<GetChannelMessagesQuery>(mpas.get_promise())
->send(it.first, std::move(input_channel), std::move(it.second),
d == nullptr ? MessageId() : d->last_new_message_id);
}
lock.set_value(Unit());
}
bool MessagesManager::is_message_edited_recently(FullMessageId full_message_id, int32 seconds) {
if (seconds < 0) {
return false;
}
if (!full_message_id.get_message_id().is_valid()) {
return false;
}
auto m = get_message_force(full_message_id, "is_message_edited_recently");
if (m == nullptr) {
return true;
}
return m->edit_date >= G()->unix_time() - seconds;
}
Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const Message *m) {
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (dialog_id.get_type() != DialogType::Channel) {
auto forward_info = m->forward_info.get();
if (!can_message_content_have_media_timestamp(m->content.get()) || forward_info == nullptr ||
forward_info->is_imported || is_forward_info_sender_hidden(forward_info) ||
!forward_info->message_id.is_valid() || !m->forward_info->message_id.is_server() ||
!forward_info->sender_dialog_id.is_valid() ||
forward_info->sender_dialog_id.get_type() != DialogType::Channel) {
return Status::Error(400, "Message links are available only for messages in supergroups and channel chats");
}
return Status::OK();
}
if (m->message_id.is_yet_unsent()) {
return Status::Error(400, "Message is not sent yet");
}
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Message is scheduled");
}
if (!m->message_id.is_server()) {
return Status::Error(400, "Message is local");
}
return Status::OK();
}
bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id) ||
!td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id())) {
return false;
}
if (m->message_id.is_scheduled() || !m->message_id.is_server()) {
return false;
}
if (m->message_id.is_scheduled() || !m->message_id.is_server()) {
return false;
}
if (is_discussion_message(dialog_id, m)) {
return false;
}
return true;
}
Result<std::pair<string, bool>> MessagesManager::get_message_link(FullMessageId full_message_id, int32 media_timestamp,
bool for_group, bool in_message_thread) {
auto dialog_id = full_message_id.get_dialog_id();
auto d = get_dialog_force(dialog_id, "get_message_link");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
auto *m = get_message_force(d, full_message_id.get_message_id(), "get_message_link");
TRY_STATUS(can_get_media_timestamp_link(dialog_id, m));
if (media_timestamp <= 0 || !can_message_content_have_media_timestamp(m->content.get())) {
media_timestamp = 0;
}
if (media_timestamp != 0) {
for_group = false;
auto duration = get_message_content_media_duration(m->content.get(), td_);
if (duration != 0 && media_timestamp > duration) {
media_timestamp = 0;
}
}
auto message_id = m->message_id;
if (dialog_id.get_type() != DialogType::Channel) {
CHECK(m->forward_info != nullptr);
CHECK(m->forward_info->sender_dialog_id.get_type() == DialogType::Channel);
dialog_id = m->forward_info->sender_dialog_id;
message_id = m->forward_info->message_id;
for_group = false;
in_message_thread = false;
auto channel_message = get_message({dialog_id, message_id});
if (channel_message != nullptr && channel_message->media_album_id == 0) {
for_group = true; // default is true
}
} else {
if (m->media_album_id == 0) {
for_group = true; // default is true
}
}
bool is_forum = is_forum_channel(dialog_id);
if (!is_forum && (!m->top_thread_message_id.is_valid() || !m->top_thread_message_id.is_server() ||
is_deleted_message(d, m->top_thread_message_id) || is_broadcast_channel(dialog_id))) {
in_message_thread = false;
}
if (!td_->auth_manager_->is_bot()) {
td_->create_handler<ExportChannelMessageLinkQuery>(Promise<Unit>())
->send(dialog_id.get_channel_id(), message_id, for_group, true);
}
SliceBuilder sb;
sb << td_->option_manager_->get_option_string("t_me_url", "https://t.me/");
if (in_message_thread && !is_forum) {
// try to generate a comment link
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)) {
auto linked_dialog_id = top_m->forward_info->from_dialog_id;
auto linked_message_id = top_m->forward_info->from_message_id;
auto linked_d = get_dialog(linked_dialog_id);
CHECK(linked_d != nullptr);
CHECK(linked_dialog_id.get_type() == DialogType::Channel);
auto *linked_m = get_message_force(linked_d, linked_message_id, "get_public_message_link");
auto channel_username = td_->contacts_manager_->get_channel_first_username(linked_dialog_id.get_channel_id());
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()) {
sb << channel_username << '/' << linked_message_id.get_server_message_id().get()
<< "?comment=" << 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_first_username(dialog_id.get_channel_id());
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 << '/' << message_id.get_server_message_id().get(), true);
}
if (is_public) {
sb << dialog_username;
} else {
sb << "c/" << dialog_id.get_channel_id().get();
}
if (in_message_thread && is_forum) {
auto top_thread_message_id = m->is_topic_message ? m->top_thread_message_id : MessageId(ServerMessageId(1));
if (top_thread_message_id != message_id) {
sb << '/' << top_thread_message_id.get_server_message_id().get();
}
in_message_thread = false;
}
sb << '/' << message_id.get_server_message_id().get();
char separator = '?';
if (in_message_thread) {
sb << separator << "thread=" << m->top_thread_message_id.get_server_message_id().get();
separator = '&';
}
if (!for_group) {
sb << separator << "single";
separator = '&';
}
if (media_timestamp > 0) {
sb << separator << "t=" << media_timestamp;
separator = '&';
}
return std::make_pair(sb.as_cslice().str(), is_public);
}
string MessagesManager::get_message_embedding_code(FullMessageId full_message_id, bool for_group,
Promise<Unit> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
auto d = get_dialog_force(dialog_id, "get_message_embedding_code");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return {};
}
if (dialog_id.get_type() != DialogType::Channel ||
td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) {
promise.set_error(Status::Error(
400, "Message embedding code is available only for messages in public supergroups and channel chats"));
return {};
}
auto *m = get_message_force(d, full_message_id.get_message_id(), "get_message_embedding_code");
if (m == nullptr) {
promise.set_error(Status::Error(400, "Message not found"));
return {};
}
if (m->message_id.is_yet_unsent()) {
promise.set_error(Status::Error(400, "Message is not sent yet"));
return {};
}
if (m->message_id.is_scheduled()) {
promise.set_error(Status::Error(400, "Message is scheduled"));
return {};
}
if (!m->message_id.is_server()) {
promise.set_error(Status::Error(400, "Message is local"));
return {};
}
if (m->media_album_id == 0) {
for_group = true; // default is true
}
auto &links = message_embedding_codes_[for_group][dialog_id].embedding_codes_;
auto it = links.find(m->message_id);
if (it == links.end()) {
td_->create_handler<ExportChannelMessageLinkQuery>(std::move(promise))
->send(dialog_id.get_channel_id(), m->message_id, for_group, false);
return {};
}
promise.set_value(Unit());
return it->second;
}
void MessagesManager::on_get_public_message_link(FullMessageId full_message_id, bool for_group, string url,
string html) {
LOG_IF(ERROR, url.empty() && html.empty()) << "Receive empty public link for " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
auto message_id = full_message_id.get_message_id();
message_embedding_codes_[for_group][dialog_id].embedding_codes_[message_id] = std::move(html);
}
void MessagesManager::get_message_link_info(Slice url, Promise<MessageLinkInfo> &&promise) {
auto r_message_link_info = LinkManager::get_message_link_info(url);
if (r_message_link_info.is_error()) {
return promise.set_error(Status::Error(400, r_message_link_info.error().message()));
}
auto info = r_message_link_info.move_as_ok();
CHECK(info.username.empty() == info.channel_id.is_valid());
bool have_dialog = info.username.empty() ? td_->contacts_manager_->have_channel_force(info.channel_id)
: resolve_dialog_username(info.username).is_valid();
if (!have_dialog) {
auto query_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), info, promise = std::move(promise)](Result<Unit> &&result) mutable {
if (result.is_error()) {
return promise.set_value(std::move(info));
}
send_closure(actor_id, &MessagesManager::on_get_message_link_dialog, std::move(info), std::move(promise));
});
if (info.username.empty()) {
td_->contacts_manager_->reload_channel(info.channel_id, std::move(query_promise));
} else {
td_->create_handler<ResolveUsernameQuery>(std::move(query_promise))->send(info.username);
}
return;
}
return on_get_message_link_dialog(std::move(info), std::move(promise));
}
void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, Promise<MessageLinkInfo> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
DialogId dialog_id;
if (info.username.empty()) {
if (!td_->contacts_manager_->have_channel(info.channel_id)) {
return promise.set_error(Status::Error(500, "Chat info not found"));
}
dialog_id = DialogId(info.channel_id);
force_create_dialog(dialog_id, "on_get_message_link_dialog");
} else {
dialog_id = resolve_dialog_username(info.username);
if (dialog_id.is_valid()) {
force_create_dialog(dialog_id, "on_get_message_link_dialog", true);
}
}
Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog");
if (d == nullptr) {
return promise.set_error(Status::Error(500, "Chat not found"));
}
auto message_id = info.message_id;
get_message_force_from_server(d, message_id,
PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info), dialog_id,
promise = std::move(promise)](Result<Unit> &&result) mutable {
if (result.is_error()) {
return promise.set_value(std::move(info));
}
send_closure(actor_id, &MessagesManager::on_get_message_link_message, std::move(info),
dialog_id, std::move(promise));
}));
}
void MessagesManager::on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id,
Promise<MessageLinkInfo> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
auto message_id = info.message_id;
Message *m = get_message_force({dialog_id, message_id}, "on_get_message_link_message");
if (info.comment_message_id == MessageId() || m == nullptr || !is_broadcast_channel(dialog_id) ||
!m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) {
return promise.set_value(std::move(info));
}
if (td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_)) {
force_create_dialog(DialogId(m->reply_info.channel_id_), "on_get_message_link_message");
on_get_message_link_discussion_message(std::move(info), DialogId(m->reply_info.channel_id_), std::move(promise));
return;
}
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info),
promise = std::move(promise)](Result<MessageThreadInfo> result) mutable {
if (result.is_error() || result.ok().message_ids.empty()) {
return promise.set_value(std::move(info));
}
send_closure(actor_id, &MessagesManager::on_get_message_link_discussion_message, std::move(info),
result.ok().dialog_id, std::move(promise));
});
td_->create_handler<GetDiscussionMessageQuery>(std::move(query_promise))
->send(dialog_id, message_id, DialogId(m->reply_info.channel_id_), MessageId());
}
void MessagesManager::on_get_message_link_discussion_message(MessageLinkInfo &&info, DialogId comment_dialog_id,
Promise<MessageLinkInfo> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(comment_dialog_id.is_valid());
info.comment_dialog_id = comment_dialog_id;
Dialog *d = get_dialog_force(comment_dialog_id, "on_get_message_link_discussion_message");
if (d == nullptr) {
return promise.set_error(Status::Error(500, "Chat not found"));
}
auto comment_message_id = info.comment_message_id;
get_message_force_from_server(
d, comment_message_id,
PromiseCreator::lambda([info = std::move(info), promise = std::move(promise)](Result<Unit> &&result) mutable {
return promise.set_value(std::move(info));
}));
}
td_api::object_ptr<td_api::messageLinkInfo> MessagesManager::get_message_link_info_object(
const MessageLinkInfo &info) const {
CHECK(info.username.empty() == info.channel_id.is_valid());
bool is_public = !info.username.empty();
DialogId dialog_id = info.comment_dialog_id.is_valid()
? info.comment_dialog_id
: (is_public ? resolve_dialog_username(info.username) : DialogId(info.channel_id));
MessageId top_thread_message_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;
const Dialog *d = get_dialog(dialog_id);
if (d == nullptr) {
dialog_id = DialogId();
top_thread_message_id = MessageId();
} else {
const Message *m = get_message(d, message_id);
if (m != nullptr) {
message = get_message_object(dialog_id, m, "get_message_link_info_object");
for_album = !info.is_single && m->media_album_id != 0;
if (info.comment_dialog_id.is_valid() || info.for_comment || m->is_topic_message) {
top_thread_message_id = m->top_thread_message_id;
} else if (is_forum_channel(dialog_id) && info.top_thread_message_id == MessageId(ServerMessageId(1))) {
// General topic
top_thread_message_id = info.top_thread_message_id;
} else {
top_thread_message_id = MessageId();
}
if (can_message_content_have_media_timestamp(m->content.get())) {
auto duration = get_message_content_media_duration(m->content.get(), td_);
if (duration == 0 || info.media_timestamp <= duration) {
media_timestamp = info.media_timestamp;
}
}
if (m->content->get_type() == MessageContentType::TopicCreate && top_thread_message_id.is_valid()) {
message = nullptr;
CHECK(!for_album);
CHECK(media_timestamp == 0);
}
} else if (!info.comment_dialog_id.is_valid() && dialog_id.get_type() == DialogType::Channel &&
!is_broadcast_channel(dialog_id)) {
top_thread_message_id = info.top_thread_message_id;
}
}
return td_api::make_object<td_api::messageLinkInfo>(is_public, dialog_id.get(), top_thread_message_id.get(),
std::move(message), media_timestamp, for_album);
}
InputDialogId MessagesManager::get_input_dialog_id(DialogId dialog_id) const {
auto input_peer = get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr || input_peer->get_id() == telegram_api::inputPeerSelf::ID) {
return InputDialogId(dialog_id);
} else {
return InputDialogId(input_peer);
}
}
void MessagesManager::sort_dialog_filter_input_dialog_ids(DialogFilter *dialog_filter, const char *source) const {
auto sort_input_dialog_ids = [contacts_manager =
td_->contacts_manager_.get()](vector<InputDialogId> &input_dialog_ids) {
std::sort(input_dialog_ids.begin(), input_dialog_ids.end(),
[contacts_manager](InputDialogId lhs, InputDialogId rhs) {
auto get_order = [contacts_manager](InputDialogId input_dialog_id) {
auto dialog_id = input_dialog_id.get_dialog_id();
if (dialog_id.get_type() != DialogType::SecretChat) {
return dialog_id.get() * 10;
}
auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
return DialogId(user_id).get() * 10 + 1;
};
return get_order(lhs) < get_order(rhs);
});
};
if (!dialog_filter->include_contacts && !dialog_filter->include_non_contacts && !dialog_filter->include_bots &&
!dialog_filter->include_groups && !dialog_filter->include_channels) {
dialog_filter->excluded_dialog_ids.clear();
}
sort_input_dialog_ids(dialog_filter->excluded_dialog_ids);
sort_input_dialog_ids(dialog_filter->included_dialog_ids);
FlatHashSet<DialogId, DialogIdHash> all_dialog_ids;
for (auto input_dialog_ids :
{&dialog_filter->pinned_dialog_ids, &dialog_filter->excluded_dialog_ids, &dialog_filter->included_dialog_ids}) {
for (const auto &input_dialog_id : *input_dialog_ids) {
auto dialog_id = input_dialog_id.get_dialog_id();
CHECK(dialog_id.is_valid());
LOG_CHECK(all_dialog_ids.insert(dialog_id).second) << source << ' ' << dialog_id << ' ' << dialog_filter;
}
}
}
Result<unique_ptr<DialogFilter>> MessagesManager::create_dialog_filter(DialogFilterId dialog_filter_id,
td_api::object_ptr<td_api::chatFilter> filter) {
CHECK(filter != nullptr);
for (auto chat_ids : {&filter->pinned_chat_ids_, &filter->excluded_chat_ids_, &filter->included_chat_ids_}) {
for (const auto &chat_id : *chat_ids) {
DialogId dialog_id(chat_id);
if (!dialog_id.is_valid()) {
return Status::Error(400, "Invalid chat identifier specified");
}
const Dialog *d = get_dialog_force(dialog_id, "create_dialog_filter");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (d->order == DEFAULT_ORDER) {
return Status::Error(400, "Chat is not in the chat list");
}
}
}
auto dialog_filter = make_unique<DialogFilter>();
dialog_filter->dialog_filter_id = dialog_filter_id;
FlatHashSet<int64> added_dialog_ids;
auto add_chats = [this, &added_dialog_ids](vector<InputDialogId> &input_dialog_ids, const vector<int64> &chat_ids) {
for (const auto &chat_id : chat_ids) {
if (!added_dialog_ids.insert(chat_id).second) {
// do not allow duplicate chat_ids
continue;
}
input_dialog_ids.push_back(get_input_dialog_id(DialogId(chat_id)));
}
};
add_chats(dialog_filter->pinned_dialog_ids, filter->pinned_chat_ids_);
add_chats(dialog_filter->included_dialog_ids, filter->included_chat_ids_);
add_chats(dialog_filter->excluded_dialog_ids, filter->excluded_chat_ids_);
dialog_filter->title = clean_name(std::move(filter->title_), MAX_DIALOG_FILTER_TITLE_LENGTH);
if (dialog_filter->title.empty()) {
return Status::Error(400, "Title must be non-empty");
}
dialog_filter->emoji = DialogFilter::get_emoji_by_icon_name(filter->icon_name_);
if (dialog_filter->emoji.empty() && !filter->icon_name_.empty()) {
return Status::Error(400, "Invalid icon name specified");
}
dialog_filter->exclude_muted = filter->exclude_muted_;
dialog_filter->exclude_read = filter->exclude_read_;
dialog_filter->exclude_archived = filter->exclude_archived_;
dialog_filter->include_contacts = filter->include_contacts_;
dialog_filter->include_non_contacts = filter->include_non_contacts_;
dialog_filter->include_bots = filter->include_bots_;
dialog_filter->include_groups = filter->include_groups_;
dialog_filter->include_channels = filter->include_channels_;
TRY_STATUS(dialog_filter->check_limits());
sort_dialog_filter_input_dialog_ids(dialog_filter.get(), "create_dialog_filter");
return std::move(dialog_filter);
}
void MessagesManager::create_dialog_filter(td_api::object_ptr<td_api::chatFilter> filter,
Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto max_dialog_filters = clamp(td_->option_manager_->get_option_integer("chat_filter_count_max"),
static_cast<int64>(0), static_cast<int64>(100));
if (dialog_filters_.size() >= narrow_cast<size_t>(max_dialog_filters)) {
return promise.set_error(Status::Error(400, "The maximum number of chat folders exceeded"));
}
if (!is_update_chat_filters_sent_) {
return promise.set_error(Status::Error(400, "Chat folders are not synchronized yet"));
}
DialogFilterId dialog_filter_id;
do {
auto min_id = static_cast<int>(DialogFilterId::min().get());
auto max_id = static_cast<int>(DialogFilterId::max().get());
dialog_filter_id = DialogFilterId(static_cast<int32>(Random::fast(min_id, max_id)));
} while (get_dialog_filter(dialog_filter_id) != nullptr || get_server_dialog_filter(dialog_filter_id) != nullptr);
auto r_dialog_filter = create_dialog_filter(dialog_filter_id, std::move(filter));
if (r_dialog_filter.is_error()) {
return promise.set_error(r_dialog_filter.move_as_error());
}
auto dialog_filter = r_dialog_filter.move_as_ok();
CHECK(dialog_filter != nullptr);
auto chat_filter_info = dialog_filter->get_chat_filter_info_object();
bool at_beginning = false;
for (const auto &recommended_dialog_filter : recommended_dialog_filters_) {
if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) {
at_beginning = true;
}
}
add_dialog_filter(std::move(dialog_filter), at_beginning, "create_dialog_filter");
if (at_beginning && main_dialog_list_position_ != 0) {
main_dialog_list_position_++;
}
save_dialog_filters();
send_update_chat_filters();
synchronize_dialog_filters();
promise.set_value(std::move(chat_filter_info));
}
void MessagesManager::edit_dialog_filter(DialogFilterId dialog_filter_id, td_api::object_ptr<td_api::chatFilter> filter,
Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
if (old_dialog_filter == nullptr) {
return promise.set_error(Status::Error(400, "Chat filter not found"));
}
CHECK(is_update_chat_filters_sent_);
auto r_dialog_filter = create_dialog_filter(dialog_filter_id, std::move(filter));
if (r_dialog_filter.is_error()) {
return promise.set_error(r_dialog_filter.move_as_error());
}
auto new_dialog_filter = r_dialog_filter.move_as_ok();
CHECK(new_dialog_filter != nullptr);
auto chat_filter_info = new_dialog_filter->get_chat_filter_info_object();
if (*new_dialog_filter == *old_dialog_filter) {
return promise.set_value(std::move(chat_filter_info));
}
edit_dialog_filter(std::move(new_dialog_filter), "edit_dialog_filter");
save_dialog_filters();
send_update_chat_filters();
synchronize_dialog_filters();
promise.set_value(std::move(chat_filter_info));
}
void MessagesManager::update_dialog_filter_on_server(unique_ptr<DialogFilter> &&dialog_filter) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(dialog_filter != nullptr);
are_dialog_filters_being_synchronized_ = true;
dialog_filter->remove_secret_chat_dialog_ids();
auto dialog_filter_id = dialog_filter->dialog_filter_id;
auto input_dialog_filter = dialog_filter->get_input_dialog_filter();
auto promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_filter = std::move(dialog_filter)](Result<Unit> result) mutable {
send_closure(actor_id, &MessagesManager::on_update_dialog_filter, std::move(dialog_filter),
result.is_error() ? result.move_as_error() : Status::OK());
});
td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))
->send(dialog_filter_id, std::move(input_dialog_filter));
}
void MessagesManager::on_update_dialog_filter(unique_ptr<DialogFilter> dialog_filter, Status result) {
CHECK(!td_->auth_manager_->is_bot());
if (result.is_error()) {
// TODO rollback dialog_filters_ changes if error isn't 429
} else {
bool is_edited = false;
for (auto &filter : server_dialog_filters_) {
if (filter->dialog_filter_id == dialog_filter->dialog_filter_id) {
if (*filter != *dialog_filter) {
filter = std::move(dialog_filter);
}
is_edited = true;
break;
}
}
if (!is_edited) {
bool at_beginning = false;
for (const auto &recommended_dialog_filter : recommended_dialog_filters_) {
if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) {
at_beginning = true;
}
}
if (at_beginning) {
server_dialog_filters_.insert(server_dialog_filters_.begin(), std::move(dialog_filter));
} else {
server_dialog_filters_.push_back(std::move(dialog_filter));
}
if (at_beginning && server_main_dialog_list_position_ != 0) {
server_main_dialog_list_position_++;
}
}
save_dialog_filters();
}
are_dialog_filters_being_synchronized_ = false;
synchronize_dialog_filters();
}
void MessagesManager::delete_dialog_filter(DialogFilterId dialog_filter_id, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
auto dialog_filter = get_dialog_filter(dialog_filter_id);
if (dialog_filter == nullptr) {
return promise.set_value(Unit());
}
int32 position = delete_dialog_filter(dialog_filter_id, "delete_dialog_filter");
if (main_dialog_list_position_ > position) {
main_dialog_list_position_--;
}
save_dialog_filters();
send_update_chat_filters();
synchronize_dialog_filters();
promise.set_value(Unit());
}
void MessagesManager::delete_dialog_filter_on_server(DialogFilterId dialog_filter_id) {
CHECK(!td_->auth_manager_->is_bot());
are_dialog_filters_being_synchronized_ = true;
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::on_delete_dialog_filter, dialog_filter_id,
result.is_error() ? result.move_as_error() : Status::OK());
});
td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))->send(dialog_filter_id, nullptr);
}
void MessagesManager::on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result) {
CHECK(!td_->auth_manager_->is_bot());
if (result.is_error()) {
// TODO rollback dialog_filters_ changes if error isn't 429
} else {
for (auto it = server_dialog_filters_.begin(); it != server_dialog_filters_.end(); ++it) {
if ((*it)->dialog_filter_id == dialog_filter_id) {
server_dialog_filters_.erase(it);
save_dialog_filters();
break;
}
}
}
are_dialog_filters_being_synchronized_ = false;
synchronize_dialog_filters();
}
void MessagesManager::reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position,
Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
for (auto dialog_filter_id : dialog_filter_ids) {
auto dialog_filter = get_dialog_filter(dialog_filter_id);
if (dialog_filter == nullptr) {
return promise.set_error(Status::Error(400, "Chat filter not found"));
}
}
std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids_set(dialog_filter_ids.begin(),
dialog_filter_ids.end());
if (new_dialog_filter_ids_set.size() != dialog_filter_ids.size()) {
return promise.set_error(Status::Error(400, "Duplicate chat filters in the new list"));
}
if (main_dialog_list_position < 0 || main_dialog_list_position > static_cast<int32>(dialog_filters_.size())) {
return promise.set_error(Status::Error(400, "Invalid main chat list position specified"));
}
if (!td_->option_manager_->get_option_boolean("is_premium")) {
main_dialog_list_position = 0;
}
if (set_dialog_filters_order(dialog_filters_, dialog_filter_ids) ||
main_dialog_list_position != main_dialog_list_position_) {
main_dialog_list_position_ = main_dialog_list_position;
save_dialog_filters();
send_update_chat_filters();
synchronize_dialog_filters();
}
promise.set_value(Unit());
}
void MessagesManager::reorder_dialog_filters_on_server(vector<DialogFilterId> dialog_filter_ids,
int32 main_dialog_list_position) {
CHECK(!td_->auth_manager_->is_bot());
are_dialog_filters_being_synchronized_ = true;
auto promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_filter_ids, main_dialog_list_position](Result<Unit> result) mutable {
send_closure(actor_id, &MessagesManager::on_reorder_dialog_filters, std::move(dialog_filter_ids),
main_dialog_list_position, result.is_error() ? result.move_as_error() : Status::OK());
});
td_->create_handler<UpdateDialogFiltersOrderQuery>(std::move(promise))
->send(dialog_filter_ids, main_dialog_list_position);
}
void MessagesManager::on_reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids,
int32 main_dialog_list_position, Status result) {
CHECK(!td_->auth_manager_->is_bot());
if (result.is_error()) {
// TODO rollback dialog_filters_ changes if error isn't 429
} else {
if (set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids)) ||
server_main_dialog_list_position_ != main_dialog_list_position) {
server_main_dialog_list_position_ = main_dialog_list_position;
save_dialog_filters();
}
}
are_dialog_filters_being_synchronized_ = false;
synchronize_dialog_filters();
}
bool MessagesManager::set_dialog_filters_order(vector<unique_ptr<DialogFilter>> &dialog_filters,
vector<DialogFilterId> dialog_filter_ids) {
auto old_dialog_filter_ids = get_dialog_filter_ids(dialog_filters, -1);
if (old_dialog_filter_ids == dialog_filter_ids) {
return false;
}
LOG(INFO) << "Reorder chat filters from " << old_dialog_filter_ids << " to " << dialog_filter_ids;
if (dialog_filter_ids.size() != old_dialog_filter_ids.size()) {
for (auto dialog_filter_id : old_dialog_filter_ids) {
if (!td::contains(dialog_filter_ids, dialog_filter_id)) {
dialog_filter_ids.push_back(dialog_filter_id);
}
}
CHECK(dialog_filter_ids.size() == old_dialog_filter_ids.size());
}
if (old_dialog_filter_ids == dialog_filter_ids) {
return false;
}
CHECK(dialog_filter_ids.size() == dialog_filters.size());
for (size_t i = 0; i < dialog_filters.size(); i++) {
for (size_t j = i; j < dialog_filters.size(); j++) {
if (dialog_filters[j]->dialog_filter_id == dialog_filter_ids[i]) {
if (i != j) {
std::swap(dialog_filters[i], dialog_filters[j]);
}
break;
}
}
CHECK(dialog_filters[i]->dialog_filter_id == dialog_filter_ids[i]);
}
return true;
}
void MessagesManager::add_dialog_filter(unique_ptr<DialogFilter> dialog_filter, bool at_beginning, const char *source) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
CHECK(dialog_filter != nullptr);
auto dialog_filter_id = dialog_filter->dialog_filter_id;
LOG(INFO) << "Add " << dialog_filter_id << " from " << source;
CHECK(get_dialog_filter(dialog_filter_id) == nullptr);
if (at_beginning) {
dialog_filters_.insert(dialog_filters_.begin(), std::move(dialog_filter));
} else {
dialog_filters_.push_back(std::move(dialog_filter));
}
auto dialog_list_id = DialogListId(dialog_filter_id);
CHECK(dialog_lists_.count(dialog_list_id) == 0);
auto &list = add_dialog_list(dialog_list_id);
auto folder_ids = get_dialog_list_folder_ids(list);
CHECK(!folder_ids.empty());
for (auto folder_id : folder_ids) {
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
for (const auto &dialog_date : folder->ordered_dialogs_) {
if (dialog_date.get_order() == DEFAULT_ORDER) {
break;
}
auto dialog_id = dialog_date.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (need_dialog_in_list(d, list)) {
list.in_memory_dialog_total_count_++;
add_dialog_to_list(d, dialog_list_id);
}
}
}
for (const auto &input_dialog_id : reversed(dialog_filters_.back()->pinned_dialog_ids)) {
auto dialog_id = input_dialog_id.get_dialog_id();
if (!dialog_id.is_valid()) {
continue;
}
auto order = get_next_pinned_dialog_order();
list.pinned_dialogs_.emplace_back(order, dialog_id);
list.pinned_dialog_id_orders_.emplace(dialog_id, order);
}
std::reverse(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end());
list.are_pinned_dialogs_inited_ = true;
update_list_last_pinned_dialog_date(list);
update_list_last_dialog_date(list);
}
void MessagesManager::edit_dialog_filter(unique_ptr<DialogFilter> new_dialog_filter, const char *source) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
CHECK(new_dialog_filter != nullptr);
LOG(INFO) << "Edit " << new_dialog_filter->dialog_filter_id << " from " << source;
for (auto &old_dialog_filter : dialog_filters_) {
if (old_dialog_filter->dialog_filter_id == new_dialog_filter->dialog_filter_id) {
CHECK(*old_dialog_filter != *new_dialog_filter);
auto dialog_list_id = DialogListId(old_dialog_filter->dialog_filter_id);
auto *old_list_ptr = get_dialog_list(dialog_list_id);
CHECK(old_list_ptr != nullptr);
auto &old_list = *old_list_ptr;
disable_get_dialog_filter_ = true; // to ensure crash if get_dialog_filter is called
auto folder_ids = get_dialog_filter_folder_ids(old_dialog_filter.get());
CHECK(!folder_ids.empty());
for (auto folder_id : get_dialog_filter_folder_ids(new_dialog_filter.get())) {
if (!td::contains(folder_ids, folder_id)) {
folder_ids.push_back(folder_id);
}
}
DialogList new_list;
new_list.dialog_list_id = dialog_list_id;
auto old_it = old_list.pinned_dialogs_.rbegin();
for (const auto &input_dialog_id : reversed(new_dialog_filter->pinned_dialog_ids)) {
auto dialog_id = input_dialog_id.get_dialog_id();
if (!dialog_id.is_valid()) {
continue;
}
while (old_it < old_list.pinned_dialogs_.rend()) {
if (old_it->get_dialog_id() == dialog_id) {
break;
}
++old_it;
}
int64 order;
if (old_it < old_list.pinned_dialogs_.rend()) {
order = old_it->get_order();
++old_it;
} else {
order = get_next_pinned_dialog_order();
}
new_list.pinned_dialogs_.emplace_back(order, dialog_id);
new_list.pinned_dialog_id_orders_.emplace(dialog_id, order);
}
std::reverse(new_list.pinned_dialogs_.begin(), new_list.pinned_dialogs_.end());
new_list.are_pinned_dialogs_inited_ = true;
do_update_list_last_pinned_dialog_date(new_list);
do_update_list_last_dialog_date(new_list, get_dialog_filter_folder_ids(new_dialog_filter.get()));
new_list.server_dialog_total_count_ = 0;
new_list.secret_chat_total_count_ = 0;
std::map<DialogDate, const Dialog *> updated_position_dialogs;
for (auto folder_id : folder_ids) {
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
for (const auto &dialog_date : folder->ordered_dialogs_) {
if (dialog_date.get_order() == DEFAULT_ORDER) {
break;
}
auto dialog_id = dialog_date.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
const DialogPositionInList old_position = get_dialog_position_in_list(old_list_ptr, d);
// can't use get_dialog_position_in_list, because need_dialog_in_list calls get_dialog_filter
DialogPositionInList new_position;
if (need_dialog_in_filter(d, new_dialog_filter.get())) {
new_position.private_order = get_dialog_private_order(&new_list, d);
if (new_position.private_order != 0) {
new_position.public_order =
DialogDate(new_position.private_order, dialog_id) <= new_list.list_last_dialog_date_
? new_position.private_order
: 0;
new_position.is_pinned = get_dialog_pinned_order(&new_list, dialog_id) != DEFAULT_ORDER;
new_position.is_sponsored = is_dialog_sponsored(d);
}
}
if (need_send_update_chat_position(old_position, new_position)) {
updated_position_dialogs.emplace(DialogDate(new_position.public_order, dialog_id), d);
}
bool was_in_list = old_position.private_order != 0;
bool is_in_list = new_position.private_order != 0;
if (is_in_list) {
if (!was_in_list) {
add_dialog_to_list(d, dialog_list_id);
}
new_list.in_memory_dialog_total_count_++;
if (dialog_id.get_type() == DialogType::SecretChat) {
new_list.secret_chat_total_count_++;
} else {
new_list.server_dialog_total_count_++;
}
auto unread_count = d->server_unread_count + d->local_unread_count;
if (unread_count != 0) {
new_list.unread_message_total_count_ += unread_count;
if (is_dialog_muted(d)) {
new_list.unread_message_muted_count_ += unread_count;
}
}
if (unread_count != 0 || d->is_marked_as_unread) {
new_list.unread_dialog_total_count_++;
if (unread_count == 0 && d->is_marked_as_unread) {
new_list.unread_dialog_marked_count_++;
}
if (is_dialog_muted(d)) {
new_list.unread_dialog_muted_count_++;
if (unread_count == 0 && d->is_marked_as_unread) {
new_list.unread_dialog_muted_marked_count_++;
}
}
}
} else {
if (was_in_list) {
remove_dialog_from_list(d, dialog_list_id);
}
}
}
}
if (new_list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
new_list.is_message_unread_count_inited_ = true;
new_list.is_dialog_unread_count_inited_ = true;
new_list.need_unread_count_recalc_ = false;
} else {
if (old_list.is_message_unread_count_inited_) { // can't stop sending updates
new_list.is_message_unread_count_inited_ = true;
}
if (old_list.is_dialog_unread_count_inited_) { // can't stop sending updates
new_list.is_dialog_unread_count_inited_ = true;
}
new_list.server_dialog_total_count_ = -1;
new_list.secret_chat_total_count_ = -1;
}
bool need_update_unread_message_count =
new_list.is_message_unread_count_inited_ &&
(old_list.unread_message_total_count_ != new_list.unread_message_total_count_ ||
old_list.unread_message_muted_count_ != new_list.unread_message_muted_count_ ||
!old_list.is_message_unread_count_inited_);
bool need_update_unread_chat_count =
new_list.is_dialog_unread_count_inited_ &&
(old_list.unread_dialog_total_count_ != new_list.unread_dialog_total_count_ ||
old_list.unread_dialog_muted_count_ != new_list.unread_dialog_muted_count_ ||
old_list.unread_dialog_marked_count_ != new_list.unread_dialog_marked_count_ ||
old_list.unread_dialog_muted_marked_count_ != new_list.unread_dialog_muted_marked_count_ ||
get_dialog_total_count(old_list) != get_dialog_total_count(new_list) ||
!old_list.is_dialog_unread_count_inited_);
bool need_save_unread_chat_count = new_list.is_dialog_unread_count_inited_ &&
(old_list.server_dialog_total_count_ != new_list.server_dialog_total_count_ ||
old_list.secret_chat_total_count_ != new_list.secret_chat_total_count_);
auto load_list_promises = std::move(old_list.load_list_queries_);
disable_get_dialog_filter_ = false;
old_list = std::move(new_list);
old_dialog_filter = std::move(new_dialog_filter);
if (need_update_unread_message_count) {
send_update_unread_message_count(old_list, DialogId(), true, source);
}
if (need_update_unread_chat_count) {
send_update_unread_chat_count(old_list, DialogId(), true, source);
} else if (need_save_unread_chat_count) {
save_unread_chat_count(old_list);
}
for (const auto &it : updated_position_dialogs) {
send_update_chat_position(dialog_list_id, it.second, source);
}
if (old_list.need_unread_count_recalc_) {
// repair unread count
get_dialogs_from_list(dialog_list_id, static_cast<int32>(old_list.pinned_dialogs_.size() + 2), Auto());
}
if (!load_list_promises.empty()) {
LOG(INFO) << "Retry loading of chats in " << dialog_list_id;
set_promises(load_list_promises); // try again
}
return;
}
}
UNREACHABLE();
}
int32 MessagesManager::delete_dialog_filter(DialogFilterId dialog_filter_id, const char *source) {
if (td_->auth_manager_->is_bot()) {
// just in case
return -1;
}
LOG(INFO) << "Delete " << dialog_filter_id << " from " << source;
for (auto it = dialog_filters_.begin(); it != dialog_filters_.end(); ++it) {
if ((*it)->dialog_filter_id == dialog_filter_id) {
auto dialog_list_id = DialogListId(dialog_filter_id);
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
auto folder_ids = get_dialog_list_folder_ids(*list);
CHECK(!folder_ids.empty());
for (auto folder_id : folder_ids) {
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
for (const auto &dialog_date : folder->ordered_dialogs_) {
if (dialog_date.get_order() == DEFAULT_ORDER) {
break;
}
auto dialog_id = dialog_date.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
const DialogPositionInList old_position = get_dialog_position_in_list(list, d);
if (is_dialog_in_list(d, dialog_list_id)) {
remove_dialog_from_list(d, dialog_list_id);
if (old_position.public_order != 0) {
send_update_chat_position(dialog_list_id, d, source);
}
}
}
}
if (G()->parameters().use_message_db) {
postponed_unread_message_count_updates_.erase(dialog_list_id);
postponed_unread_chat_count_updates_.erase(dialog_list_id);
if (list->is_message_unread_count_inited_) {
list->unread_message_total_count_ = 0;
list->unread_message_muted_count_ = 0;
send_update_unread_message_count(*list, DialogId(), true, source, true);
G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_message_count" << dialog_list_id.get());
}
if (list->is_dialog_unread_count_inited_) {
list->unread_dialog_total_count_ = 0;
list->unread_dialog_muted_count_ = 0;
list->unread_dialog_marked_count_ = 0;
list->unread_dialog_muted_marked_count_ = 0;
list->in_memory_dialog_total_count_ = 0;
list->server_dialog_total_count_ = 0;
list->secret_chat_total_count_ = 0;
send_update_unread_chat_count(*list, DialogId(), true, source, true);
G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_dialog_count" << dialog_list_id.get());
}
}
fail_promises(list->load_list_queries_, Status::Error(400, "Chat list not found"));
auto position = static_cast<int32>(it - dialog_filters_.begin());
dialog_lists_.erase(dialog_list_id);
dialog_filters_.erase(it);
return position;
}
}
UNREACHABLE();
return -1;
}
Status MessagesManager::delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) {
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "Bots can't delete chat reply markup");
}
if (message_id.is_scheduled()) {
return Status::Error(400, "Wrong message identifier specified");
}
if (!message_id.is_valid()) {
return Status::Error(400, "Invalid message identifier specified");
}
Dialog *d = get_dialog_force(dialog_id, "delete_dialog_reply_markup");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (d->reply_markup_message_id != message_id) {
return Status::OK();
}
Message *m = get_message_force(d, message_id, "delete_dialog_reply_markup");
CHECK(m != nullptr);
CHECK(m->reply_markup != nullptr);
if (m->reply_markup->type == ReplyMarkup::Type::ForceReply) {
set_dialog_reply_markup(d, MessageId());
} else if (m->reply_markup->type == ReplyMarkup::Type::ShowKeyboard) {
if (!m->reply_markup->is_one_time_keyboard) {
return Status::Error(400, "Do not need to delete non one-time keyboard");
}
if (m->reply_markup->is_personal) {
m->reply_markup->is_personal = false;
set_dialog_reply_markup(d, message_id);
on_message_changed(d, m, true, "delete_dialog_reply_markup");
}
} else {
// non-bots can't have messages with RemoveKeyboard
UNREACHABLE();
}
return Status::OK();
}
class MessagesManager::SaveDialogDraftMessageOnServerLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
Status MessagesManager::set_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
tl_object_ptr<td_api::draftMessage> &&draft_message) {
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "Bots can't change chat draft message");
}
Dialog *d = get_dialog_force(dialog_id, "set_dialog_draft_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
TRY_RESULT(new_draft_message, get_draft_message(td_, dialog_id, std::move(draft_message)));
if (new_draft_message != nullptr) {
new_draft_message->reply_to_message_id =
get_reply_to_message_id(d, top_thread_message_id, new_draft_message->reply_to_message_id, true);
if (!new_draft_message->reply_to_message_id.is_valid() && new_draft_message->input_message_text.text.text.empty()) {
new_draft_message = nullptr;
}
}
if (top_thread_message_id != MessageId()) {
CHECK(top_thread_message_id.is_valid());
CHECK(top_thread_message_id.is_server());
auto m = get_message_force(d, top_thread_message_id, "set_dialog_draft_message");
if (m == nullptr || m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) {
return Status::OK();
}
auto &old_draft_message = m->thread_draft_message;
if (((new_draft_message == nullptr) != (old_draft_message == nullptr)) ||
(new_draft_message != nullptr &&
(old_draft_message->reply_to_message_id != new_draft_message->reply_to_message_id ||
old_draft_message->input_message_text != new_draft_message->input_message_text))) {
old_draft_message = std::move(new_draft_message);
on_message_changed(d, m, false, "set_dialog_draft_message");
}
return Status::OK();
}
if (update_dialog_draft_message(d, std::move(new_draft_message), false, true)) {
if (dialog_id.get_type() != DialogType::SecretChat) {
if (G()->parameters().use_message_db) {
SaveDialogDraftMessageOnServerLogEvent log_event;
log_event.dialog_id_ = dialog_id;
add_log_event(d->save_draft_message_log_event_id, get_log_event_storer(log_event),
LogEvent::HandlerType::SaveDialogDraftMessageOnServer, "draft");
}
pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), d->is_opened ? MIN_SAVE_DRAFT_DELAY : 0);
}
}
return Status::OK();
}
void MessagesManager::save_dialog_draft_message_on_server(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
Promise<Unit> promise;
if (d->save_draft_message_log_event_id.log_event_id != 0) {
d->save_draft_message_log_event_id.generation++;
promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
generation = d->save_draft_message_log_event_id.generation](Result<Unit> result) {
if (!G()->close_flag()) {
send_closure(actor_id, &MessagesManager::on_saved_dialog_draft_message, dialog_id, generation);
}
});
}
// TODO do not send two queries simultaneously or use InvokeAfter
td_->create_handler<SaveDraftMessageQuery>(std::move(promise))->send(dialog_id, d->draft_message);
}
void MessagesManager::on_saved_dialog_draft_message(DialogId dialog_id, uint64 generation) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_log_event(d->save_draft_message_log_event_id, generation, "draft");
}
void MessagesManager::clear_all_draft_messages(bool exclude_secret_chats, Promise<Unit> &&promise) {
if (!exclude_secret_chats) {
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
Dialog *d = dialog.get();
if (dialog_id.get_type() == DialogType::SecretChat) {
update_dialog_draft_message(d, nullptr, false, true);
}
});
}
td_->create_handler<ClearAllDraftsQuery>(std::move(promise))->send();
}
int32 MessagesManager::get_pinned_dialogs_limit(DialogListId dialog_list_id) const {
if (dialog_list_id.is_filter()) {
return DialogFilter::get_max_filter_dialogs();
}
Slice key{"pinned_chat_count_max"};
int32 default_limit = 5;
if (!dialog_list_id.is_folder() || dialog_list_id.get_folder_id() != FolderId::main()) {
key = Slice("pinned_archived_chat_count_max");
default_limit = 100;
}
int32 limit = clamp(narrow_cast<int32>(td_->option_manager_->get_option_integer(key)), 0, 1000);
if (limit <= 0) {
if (td_->option_manager_->get_option_boolean("is_premium")) {
default_limit *= 2;
}
return default_limit;
}
return limit;
}
vector<DialogId> MessagesManager::remove_secret_chat_dialog_ids(vector<DialogId> dialog_ids) {
td::remove_if(dialog_ids, [](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; });
return dialog_ids;
}
Status MessagesManager::toggle_dialog_is_pinned(DialogListId dialog_list_id, DialogId dialog_id, bool is_pinned) {
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "Bots can't change chat pin state");
}
Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_pinned");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (d->order == DEFAULT_ORDER && is_pinned) {
return Status::Error(400, "The chat can't be pinned");
}
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr) {
return Status::Error(400, "Chat list not found");
}
if (!list->are_pinned_dialogs_inited_) {
return Status::Error(400, "Pinned chats must be loaded first");
}
bool was_pinned = is_dialog_pinned(dialog_list_id, dialog_id);
if (is_pinned == was_pinned) {
return Status::OK();
}
if (dialog_list_id.is_filter()) {
CHECK(is_update_chat_filters_sent_);
auto dialog_filter_id = dialog_list_id.get_filter_id();
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
CHECK(old_dialog_filter != nullptr);
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
if (is_pinned) {
new_dialog_filter->pinned_dialog_ids.insert(new_dialog_filter->pinned_dialog_ids.begin(),
get_input_dialog_id(dialog_id));
InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id);
InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
} else {
bool is_removed = InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id);
CHECK(is_removed);
new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
}
TRY_STATUS(new_dialog_filter->check_limits());
sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "toggle_dialog_is_pinned");
edit_dialog_filter(std::move(new_dialog_filter), "toggle_dialog_is_pinned");
save_dialog_filters();
send_update_chat_filters();
if (dialog_id.get_type() != DialogType::SecretChat) {
synchronize_dialog_filters();
}
return Status::OK();
}
CHECK(dialog_list_id.is_folder());
auto folder_id = dialog_list_id.get_folder_id();
if (is_pinned) {
if (d->folder_id != folder_id) {
return Status::Error(400, "Chat not in the list");
}
auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id);
auto pinned_dialog_count = pinned_dialog_ids.size();
auto secret_pinned_dialog_count =
std::count_if(pinned_dialog_ids.begin(), pinned_dialog_ids.end(),
[](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; });
size_t dialog_count = dialog_id.get_type() == DialogType::SecretChat
? secret_pinned_dialog_count
: pinned_dialog_count - secret_pinned_dialog_count;
if (dialog_count >= static_cast<size_t>(get_pinned_dialogs_limit(dialog_list_id))) {
return Status::Error(400, "The maximum number of pinned chats exceeded");
}
}
if (set_dialog_is_pinned(dialog_list_id, d, is_pinned)) {
toggle_dialog_is_pinned_on_server(dialog_id, is_pinned, 0);
}
return Status::OK();
}
class MessagesManager::ToggleDialogIsPinnedOnServerLogEvent {
public:
DialogId dialog_id_;
bool is_pinned_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(is_pinned_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_pinned_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_toggle_dialog_is_pinned_on_server_log_event(DialogId dialog_id, bool is_pinned) {
ToggleDialogIsPinnedOnServerLogEvent log_event{dialog_id, is_pinned};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsPinnedOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 log_event_id) {
CHECK(!td_->auth_manager_->is_bot());
if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) {
// don't even create new binlog events
return;
}
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_toggle_dialog_is_pinned_on_server_log_event(dialog_id, is_pinned);
}
td_->create_handler<ToggleDialogPinQuery>(get_erase_log_event_promise(log_event_id))->send(dialog_id, is_pinned);
}
Status MessagesManager::set_pinned_dialogs(DialogListId dialog_list_id, vector<DialogId> dialog_ids) {
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "Bots can't reorder pinned chats");
}
int32 dialog_count = 0;
int32 secret_dialog_count = 0;
auto dialog_count_limit = get_pinned_dialogs_limit(dialog_list_id);
FlatHashSet<DialogId, DialogIdHash> new_pinned_dialog_ids;
for (auto dialog_id : dialog_ids) {
Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (d->order == DEFAULT_ORDER) {
return Status::Error(400, "The chat can't be pinned");
}
if (dialog_list_id.is_folder() && d->folder_id != dialog_list_id.get_folder_id()) {
return Status::Error(400, "Chat not in the list");
}
if (dialog_id.get_type() == DialogType::SecretChat) {
secret_dialog_count++;
} else {
dialog_count++;
}
if (dialog_count > dialog_count_limit || secret_dialog_count > dialog_count_limit) {
return Status::Error(400, "The maximum number of pinned chats exceeded");
}
new_pinned_dialog_ids.insert(dialog_id);
}
if (new_pinned_dialog_ids.size() != dialog_ids.size()) {
return Status::Error(400, "Duplicate chats in the list of pinned chats");
}
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr) {
return Status::Error(400, "Chat list not found");
}
if (!list->are_pinned_dialogs_inited_) {
return Status::Error(400, "Pinned chats must be loaded first");
}
auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id);
if (pinned_dialog_ids == dialog_ids) {
return Status::OK();
}
LOG(INFO) << "Reorder pinned chats in " << dialog_list_id << " from " << pinned_dialog_ids << " to " << dialog_ids;
auto server_old_dialog_ids = remove_secret_chat_dialog_ids(pinned_dialog_ids);
auto server_new_dialog_ids = remove_secret_chat_dialog_ids(dialog_ids);
if (dialog_list_id.is_filter()) {
CHECK(is_update_chat_filters_sent_);
auto dialog_filter_id = dialog_list_id.get_filter_id();
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
CHECK(old_dialog_filter != nullptr);
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
auto old_pinned_dialog_ids = std::move(new_dialog_filter->pinned_dialog_ids);
new_dialog_filter->pinned_dialog_ids =
transform(dialog_ids, [this](DialogId dialog_id) { return get_input_dialog_id(dialog_id); });
auto is_new_pinned = [&new_pinned_dialog_ids](InputDialogId input_dialog_id) {
return new_pinned_dialog_ids.count(input_dialog_id.get_dialog_id()) > 0;
};
td::remove_if(old_pinned_dialog_ids, is_new_pinned);
td::remove_if(new_dialog_filter->included_dialog_ids, is_new_pinned);
td::remove_if(new_dialog_filter->excluded_dialog_ids, is_new_pinned);
append(new_dialog_filter->included_dialog_ids, old_pinned_dialog_ids);
TRY_STATUS(new_dialog_filter->check_limits());
sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "set_pinned_dialogs");
edit_dialog_filter(std::move(new_dialog_filter), "set_pinned_dialogs");
save_dialog_filters();
send_update_chat_filters();
if (server_old_dialog_ids != server_new_dialog_ids) {
synchronize_dialog_filters();
}
return Status::OK();
}
CHECK(dialog_list_id.is_folder());
std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
std::reverse(dialog_ids.begin(), dialog_ids.end());
FlatHashSet<DialogId, DialogIdHash> old_pinned_dialog_ids;
for (auto dialog_id : pinned_dialog_ids) {
old_pinned_dialog_ids.insert(dialog_id);
}
auto old_it = pinned_dialog_ids.begin();
for (auto dialog_id : dialog_ids) {
old_pinned_dialog_ids.erase(dialog_id);
while (old_it < pinned_dialog_ids.end()) {
if (*old_it == dialog_id) {
break;
}
++old_it;
}
if (old_it < pinned_dialog_ids.end()) {
// leave dialog where it is
++old_it;
continue;
}
set_dialog_is_pinned(dialog_id, true);
}
for (auto dialog_id : old_pinned_dialog_ids) {
Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs 2");
if (d == nullptr) {
LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << dialog_list_id;
force_create_dialog(dialog_id, "set_pinned_dialogs", true);
d = get_dialog_force(dialog_id, "set_pinned_dialogs 3");
}
if (d != nullptr) {
set_dialog_is_pinned(dialog_list_id, d, false);
}
}
if (server_old_dialog_ids != server_new_dialog_ids) {
reorder_pinned_dialogs_on_server(dialog_list_id.get_folder_id(), server_new_dialog_ids, 0);
}
return Status::OK();
}
class MessagesManager::ReorderPinnedDialogsOnServerLogEvent {
public:
FolderId folder_id_;
vector<DialogId> dialog_ids_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(folder_id_, storer);
td::store(dialog_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
if (parser.version() >= static_cast<int32>(Version::AddFolders)) {
td::parse(folder_id_, parser);
} else {
folder_id_ = FolderId();
}
td::parse(dialog_ids_, parser);
}
};
uint64 MessagesManager::save_reorder_pinned_dialogs_on_server_log_event(FolderId folder_id,
const vector<DialogId> &dialog_ids) {
ReorderPinnedDialogsOnServerLogEvent log_event{folder_id, dialog_ids};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReorderPinnedDialogsOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::reorder_pinned_dialogs_on_server(FolderId folder_id, const vector<DialogId> &dialog_ids,
uint64 log_event_id) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_reorder_pinned_dialogs_on_server_log_event(folder_id, dialog_ids);
}
td_->create_handler<ReorderPinnedDialogsQuery>(get_erase_log_event_promise(log_event_id))
->send(folder_id, dialog_ids);
}
Status MessagesManager::toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) {
Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_marked_as_unread");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (is_marked_as_unread == d->is_marked_as_unread) {
return Status::OK();
}
set_dialog_is_marked_as_unread(d, is_marked_as_unread);
toggle_dialog_is_marked_as_unread_on_server(dialog_id, is_marked_as_unread, 0);
return Status::OK();
}
class MessagesManager::ToggleDialogIsMarkedAsUnreadOnServerLogEvent {
public:
DialogId dialog_id_;
bool is_marked_as_unread_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(is_marked_as_unread_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_marked_as_unread_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_toggle_dialog_is_marked_as_unread_on_server_log_event(DialogId dialog_id,
bool is_marked_as_unread) {
ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event{dialog_id, is_marked_as_unread};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread,
uint64 log_event_id) {
if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) {
// don't even create new binlog events
return;
}
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_toggle_dialog_is_marked_as_unread_on_server_log_event(dialog_id, is_marked_as_unread);
}
td_->create_handler<ToggleDialogUnreadMarkQuery>(get_erase_log_event_promise(log_event_id))
->send(dialog_id, is_marked_as_unread);
}
Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_ptr<td_api::MessageSender> &sender,
bool is_blocked) {
TRY_RESULT(dialog_id, get_message_sender_dialog_id(td_, sender, true, false));
switch (dialog_id.get_type()) {
case DialogType::User:
if (dialog_id == get_my_dialog_id()) {
return Status::Error(400, is_blocked ? Slice("Can't block self") : Slice("Can't unblock self"));
}
break;
case DialogType::Chat:
return Status::Error(400, "Basic group chats can't be blocked");
case DialogType::Channel:
// ok
break;
case DialogType::SecretChat: {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id)) {
return Status::Error(400, "The secret chat can't be blocked");
}
dialog_id = DialogId(user_id);
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
Dialog *d = get_dialog_force(dialog_id, "toggle_message_sender_is_blocked");
if (!have_input_peer(dialog_id, AccessRights::Know)) {
return Status::Error(400, "Message sender isn't accessible");
}
if (d != nullptr) {
if (is_blocked == d->is_blocked) {
return Status::OK();
}
set_dialog_is_blocked(d, is_blocked);
} else {
CHECK(dialog_id.get_type() == DialogType::User);
td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked);
}
toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, 0);
return Status::OK();
}
class MessagesManager::ToggleDialogIsBlockedOnServerLogEvent {
public:
DialogId dialog_id_;
bool is_blocked_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(is_blocked_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_blocked_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked) {
ToggleDialogIsBlockedOnServerLogEvent log_event{dialog_id, is_blocked};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsBlockedOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked);
}
td_->create_handler<ToggleDialogIsBlockedQuery>(get_erase_log_event_promise(log_event_id))
->send(dialog_id, is_blocked);
}
Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) {
CHECK(!td_->auth_manager_->is_bot());
Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_silent_send_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
if (update_dialog_silent_send_message(d, silent_send_message)) {
update_dialog_notification_settings_on_server(dialog_id, false);
}
return Status::OK();
}
class MessagesManager::UpdateDialogNotificationSettingsOnServerLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
void MessagesManager::update_dialog_notification_settings_on_server(DialogId dialog_id, bool from_binlog) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (!from_binlog && td_->notification_settings_manager_->get_input_notify_peer(dialog_id, MessageId()) == nullptr) {
// don't even create new binlog events
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!from_binlog && G()->parameters().use_message_db) {
UpdateDialogNotificationSettingsOnServerLogEvent log_event;
log_event.dialog_id_ = dialog_id;
add_log_event(d->save_notification_settings_log_event_id, get_log_event_storer(log_event),
LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer, "notification settings");
}
Promise<Unit> promise;
if (d->save_notification_settings_log_event_id.log_event_id != 0) {
d->save_notification_settings_log_event_id.generation++;
promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_id,
generation = d->save_notification_settings_log_event_id.generation](Result<Unit> result) {
if (!G()->close_flag()) {
send_closure(actor_id, &MessagesManager::on_updated_dialog_notification_settings, dialog_id, generation);
}
});
}
send_update_dialog_notification_settings_query(d, std::move(promise));
}
void MessagesManager::send_update_dialog_notification_settings_query(const Dialog *d, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
// TODO do not send two queries simultaneously or use InvokeAfter
td_->notification_settings_manager_->update_dialog_notify_settings(d->dialog_id, MessageId(),
d->notification_settings, std::move(promise));
}
void MessagesManager::on_updated_dialog_notification_settings(DialogId dialog_id, uint64 generation) {
CHECK(!td_->auth_manager_->is_bot());
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_log_event(d->save_notification_settings_log_event_id, generation, "notification settings");
}
Status MessagesManager::set_dialog_client_data(DialogId dialog_id, string &&client_data) {
Dialog *d = get_dialog_force(dialog_id, "set_dialog_client_data");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
d->client_data = std::move(client_data);
on_dialog_updated(d->dialog_id, "set_dialog_client_data");
return Status::OK();
}
bool MessagesManager::is_dialog_inited(const Dialog *d) {
return d != nullptr && d->notification_settings.is_synchronized && d->is_last_read_inbox_message_id_inited &&
d->is_last_read_outbox_message_id_inited;
}
int32 MessagesManager::get_dialog_mute_until(const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
if (d->notification_settings.use_default_mute_until) {
auto scope = get_dialog_notification_setting_scope(d->dialog_id);
return td_->notification_settings_manager_->get_scope_mute_until(scope);
} else {
return d->notification_settings.mute_until;
}
}
bool MessagesManager::is_dialog_muted(const Dialog *d) const {
return get_dialog_mute_until(d) != 0;
}
bool MessagesManager::is_dialog_pinned_message_notifications_disabled(const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
if (d->notification_settings.use_default_disable_pinned_message_notifications) {
auto scope = get_dialog_notification_setting_scope(d->dialog_id);
return td_->notification_settings_manager_->get_scope_disable_pinned_message_notifications(scope);
}
return d->notification_settings.disable_pinned_message_notifications;
}
bool MessagesManager::is_dialog_mention_notifications_disabled(const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
if (d->notification_settings.use_default_disable_mention_notifications) {
auto scope = get_dialog_notification_setting_scope(d->dialog_id);
return td_->notification_settings_manager_->get_scope_disable_mention_notifications(scope);
}
return d->notification_settings.disable_mention_notifications;
}
void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise<Unit> &&promise) {
if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (!have_dialog_info_force(dialog_id)) {
return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
}
if (force || td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
force_create_dialog(dialog_id, "create dialog");
} else {
const Dialog *d = get_dialog_force(dialog_id, "create_dialog");
if (!is_dialog_inited(d)) {
return send_get_dialog_query(dialog_id, std::move(promise), 0, "create_dialog");
}
}
promise.set_value(Unit());
}
DialogId MessagesManager::create_new_group_chat(const vector<UserId> &user_ids, const string &title,
MessageTtl message_ttl, int64 &random_id, Promise<Unit> &&promise) {
LOG(INFO) << "Trying to create group chat \"" << title << "\" with members " << format::as_array(user_ids);
if (random_id != 0) {
// request has already been sent before
auto it = created_dialogs_.find(random_id);
CHECK(it != created_dialogs_.end());
auto dialog_id = it->second;
CHECK(dialog_id.get_type() == DialogType::Chat);
CHECK(have_dialog(dialog_id));
created_dialogs_.erase(it);
// set default notification settings to newly created chat
on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_group_chat");
promise.set_value(Unit());
return dialog_id;
}
if (user_ids.empty()) {
promise.set_error(Status::Error(400, "Too few users to create basic group chat"));
return DialogId();
}
auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
promise.set_error(Status::Error(400, "Title must be non-empty"));
return DialogId();
}
vector<tl_object_ptr<telegram_api::InputUser>> input_users;
for (auto user_id : user_ids) {
auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
if (r_input_user.is_error()) {
promise.set_error(r_input_user.move_as_error());
return DialogId();
}
input_users.push_back(r_input_user.move_as_ok());
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || created_dialogs_.count(random_id) > 0);
created_dialogs_[random_id]; // reserve place for result
td_->create_handler<CreateChatQuery>(std::move(promise))
->send(std::move(input_users), new_title, message_ttl, random_id);
return DialogId();
}
DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_megagroup, const string &description,
const DialogLocation &location, bool for_import,
MessageTtl message_ttl, int64 &random_id, Promise<Unit> &&promise) {
LOG(INFO) << "Trying to create " << (is_megagroup ? "supergroup" : "broadcast") << " with title \"" << title
<< "\", description \"" << description << "\" and " << location;
if (random_id != 0) {
// request has already been sent before
auto it = created_dialogs_.find(random_id);
CHECK(it != created_dialogs_.end());
auto dialog_id = it->second;
CHECK(dialog_id.get_type() == DialogType::Channel);
CHECK(have_dialog(dialog_id));
created_dialogs_.erase(it);
// set default notification settings to newly created chat
on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_channel_chat");
promise.set_value(Unit());
return dialog_id;
}
auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
promise.set_error(Status::Error(400, "Title must be non-empty"));
return DialogId();
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || created_dialogs_.count(random_id) > 0);
created_dialogs_[random_id]; // reserve place for result
td_->create_handler<CreateChannelQuery>(std::move(promise))
->send(new_title, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, for_import,
message_ttl, random_id);
return DialogId();
}
void MessagesManager::create_new_secret_chat(UserId user_id, Promise<SecretChatId> &&promise) {
auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
if (r_input_user.is_error()) {
return promise.set_error(r_input_user.move_as_error());
}
if (r_input_user.ok()->get_id() != telegram_api::inputUser::ID) {
return promise.set_error(Status::Error(400, "Can't create secret chat with self"));
}
auto user = static_cast<const telegram_api::inputUser *>(r_input_user.ok().get());
send_closure(G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_),
user->access_hash_, std::move(promise));
}
DialogId MessagesManager::migrate_dialog_to_megagroup(DialogId dialog_id, Promise<Unit> &&promise) {
LOG(INFO) << "Trying to convert " << dialog_id << " to supergroup";
if (dialog_id.get_type() != DialogType::Chat) {
promise.set_error(Status::Error(400, "Only basic group chats can be converted to supergroup"));
return DialogId();
}
auto channel_id = td_->contacts_manager_->migrate_chat_to_megagroup(dialog_id.get_chat_id(), promise);
if (!channel_id.is_valid()) {
return DialogId();
}
if (!td_->contacts_manager_->have_channel(channel_id)) {
LOG(ERROR) << "Can't find info about supergroup to which the group has migrated";
promise.set_error(Status::Error(400, "Supergroup is not found"));
return DialogId();
}
auto new_dialog_id = DialogId(channel_id);
Dialog *d = get_dialog_force(new_dialog_id, "migrate_dialog_to_megagroup");
if (d == nullptr) {
d = add_dialog(new_dialog_id, "migrate_dialog_to_megagroup");
if (d->pts == 0) {
d->pts = 1;
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, d->pts, "migrate");
}
}
update_dialog_pos(d, "migrate_dialog_to_megagroup");
}
promise.set_value(Unit());
return new_dialog_id;
}
bool MessagesManager::is_dialog_opened(DialogId dialog_id) const {
const Dialog *d = get_dialog(dialog_id);
return d != nullptr && d->is_opened;
}
Status MessagesManager::open_dialog(DialogId dialog_id) {
Dialog *d = get_dialog_force(dialog_id, "open_dialog");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
open_dialog(d);
return Status::OK();
}
Status MessagesManager::close_dialog(DialogId dialog_id) {
Dialog *d = get_dialog_force(dialog_id, "close_dialog");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
close_dialog(d);
return Status::OK();
}
DialogId MessagesManager::get_my_dialog_id() const {
return DialogId(td_->contacts_manager_->get_my_id());
}
Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_message_id,
const vector<MessageId> &message_ids, bool force_read) {
CHECK(!td_->auth_manager_->is_bot());
Dialog *d = get_dialog_force(dialog_id, "view_messages");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
for (auto message_id : message_ids) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
if (message_id.is_valid_sponsored()) {
if (d->is_opened) {
td_->sponsored_message_manager_->view_sponsored_message(dialog_id, message_id);
}
continue;
}
return Status::Error(400, "Invalid message identifier");
}
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
MessageId max_thread_message_id;
if (top_thread_message_id != MessageId()) {
if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
return Status::Error(400, "Invalid message thread ID specified");
}
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
return Status::Error(400, "There are no message threads in the chat");
}
const auto *top_m = get_message_force(d, top_thread_message_id, "view_messages 6");
if (top_m != nullptr && !top_m->reply_info.is_comment_) {
max_thread_message_id = top_m->reply_info.max_message_id_;
}
}
bool need_read = force_read || d->is_opened;
MessageId max_message_id; // max server or local viewed message_id
vector<MessageId> read_content_message_ids;
vector<MessageId> new_viewed_message_ids;
vector<MessageId> viewed_reaction_message_ids;
for (auto message_id : message_ids) {
if (!message_id.is_valid()) {
continue;
}
auto *m = get_message_force(d, message_id, "view_messages 1");
if (m != nullptr) {
if (m->message_id.is_server() && m->view_count > 0) {
d->pending_viewed_message_ids.insert(m->message_id);
}
if (!m->message_id.is_yet_unsent() && m->message_id > max_message_id) {
max_message_id = m->message_id;
}
auto message_content_type = m->content->get_type();
if (message_content_type == MessageContentType::LiveLocation) {
on_message_live_location_viewed(d, m);
}
if (need_read && message_content_type != MessageContentType::VoiceNote &&
message_content_type != MessageContentType::VideoNote &&
update_message_contains_unread_mention(d, m, false, "view_messages")) {
CHECK(m->message_id.is_server());
read_content_message_ids.push_back(m->message_id);
on_message_changed(d, m, true, "view_messages");
}
if (need_read && remove_message_unread_reactions(d, m, "view_messages")) {
CHECK(m->message_id.is_server());
read_content_message_ids.push_back(m->message_id);
on_message_changed(d, m, true, "view_messages");
}
auto file_source_id = full_message_id_to_file_source_id_.get({dialog_id, m->message_id});
if (file_source_id.is_valid()) {
LOG(INFO) << "Have " << file_source_id << " for " << m->message_id;
CHECK(file_source_id.is_valid());
for (auto file_id : get_message_file_ids(m)) {
auto file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.empty());
send_closure(td_->download_manager_actor_, &DownloadManager::update_file_viewed, file_view.get_main_file_id(),
file_source_id);
}
}
if (m->message_id.is_server() && d->is_opened) {
auto &info = dialog_viewed_messages_[dialog_id];
if (info == nullptr) {
info = make_unique<ViewedMessagesInfo>();
}
auto &view_id = info->message_id_to_view_id[message_id];
if (view_id == 0) {
new_viewed_message_ids.push_back(message_id);
if (need_poll_message_reactions(d, m)) {
viewed_reaction_message_ids.push_back(message_id);
}
} else {
info->recently_viewed_messages.erase(view_id);
}
view_id = ++info->current_view_id;
info->recently_viewed_messages[view_id] = message_id;
}
} else if (!message_id.is_yet_unsent() && message_id > max_message_id) {
if (message_id <= d->max_notification_message_id || message_id <= d->last_new_message_id ||
message_id <= max_thread_message_id) {
max_message_id = message_id;
}
}
}
if (!d->pending_viewed_message_ids.empty()) {
pending_message_views_timeout_.add_timeout_in(dialog_id.get(), MAX_MESSAGE_VIEW_DELAY);
d->increment_view_counter |= d->is_opened;
}
if (!read_content_message_ids.empty()) {
read_message_contents_on_server(dialog_id, std::move(read_content_message_ids), 0, Auto());
}
if (!new_viewed_message_ids.empty()) {
LOG(INFO) << "Have new viewed " << new_viewed_message_ids;
auto &info = dialog_viewed_messages_[dialog_id];
CHECK(info != nullptr);
CHECK(info->message_id_to_view_id.size() == info->recently_viewed_messages.size());
constexpr size_t MAX_RECENTLY_VIEWED_MESSAGES = 50;
while (info->recently_viewed_messages.size() > MAX_RECENTLY_VIEWED_MESSAGES) {
auto it = info->recently_viewed_messages.begin();
info->message_id_to_view_id.erase(it->second);
info->recently_viewed_messages.erase(it);
}
if (!viewed_reaction_message_ids.empty()) {
queue_message_reactions_reload(dialog_id, viewed_reaction_message_ids);
}
}
if (td_->is_online() && dialog_viewed_messages_.count(dialog_id) != 0) {
update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD);
}
if (!need_read) {
return Status::OK();
}
if (top_thread_message_id.is_valid() && max_message_id.is_valid()) {
MessageId prev_last_read_inbox_message_id;
max_thread_message_id = MessageId();
Message *top_m = get_message_force(d, top_thread_message_id, "view_messages 2");
if (top_m != nullptr && is_active_message_reply_info(dialog_id, top_m->reply_info)) {
prev_last_read_inbox_message_id = top_m->reply_info.last_read_inbox_message_id_;
if (top_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) {
on_message_reply_info_changed(dialog_id, top_m);
on_message_changed(d, top_m, true, "view_messages 3");
}
max_thread_message_id = top_m->reply_info.max_message_id_;
if (is_discussion_message(dialog_id, top_m)) {
auto linked_dialog_id = top_m->forward_info->from_dialog_id;
auto linked_d = get_dialog(linked_dialog_id);
CHECK(linked_d != nullptr);
CHECK(linked_dialog_id.get_type() == DialogType::Channel);
auto *linked_m = get_message_force(linked_d, top_m->forward_info->from_message_id, "view_messages 4");
if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info)) {
if (linked_m->reply_info.last_read_inbox_message_id_ < prev_last_read_inbox_message_id) {
prev_last_read_inbox_message_id = linked_m->reply_info.last_read_inbox_message_id_;
}
if (linked_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) {
on_message_reply_info_changed(linked_dialog_id, linked_m);
on_message_changed(linked_d, linked_m, true, "view_messages 5");
}
if (linked_m->reply_info.max_message_id_ > max_thread_message_id) {
max_thread_message_id = linked_m->reply_info.max_message_id_;
}
}
}
}
if (max_message_id.get_prev_server_message_id().get() >
prev_last_read_inbox_message_id.get_prev_server_message_id().get()) {
read_message_thread_history_on_server(d, top_thread_message_id, max_message_id.get_prev_server_message_id(),
max_thread_message_id.get_prev_server_message_id());
}
return Status::OK();
}
if (max_message_id > d->last_read_inbox_message_id) {
const MessageId last_read_message_id = max_message_id;
const MessageId prev_last_read_inbox_message_id = d->last_read_inbox_message_id;
MessageId read_history_on_server_message_id;
if (dialog_id.get_type() != DialogType::SecretChat) {
if (last_read_message_id.get_prev_server_message_id().get() >
prev_last_read_inbox_message_id.get_prev_server_message_id().get()) {
read_history_on_server_message_id = last_read_message_id.get_prev_server_message_id();
}
} else {
if (last_read_message_id > prev_last_read_inbox_message_id) {
read_history_on_server_message_id = last_read_message_id;
}
}
if (read_history_on_server_message_id.is_valid()) {
// add dummy timeout to not try to repair unread_count in read_history_inbox before server request succeeds
// the timeout will be overwritten in the read_history_on_server call
pending_read_history_timeout_.add_timeout_in(dialog_id.get(), 0);
}
read_history_inbox(d->dialog_id, last_read_message_id, -1, "view_messages");
if (read_history_on_server_message_id.is_valid()) {
// call read_history_on_server after read_history_inbox to not have delay before request if all messages are read
read_history_on_server(d, read_history_on_server_message_id);
}
}
if (d->is_marked_as_unread) {
set_dialog_is_marked_as_unread(d, false);
}
return Status::OK();
}
void MessagesManager::finish_get_message_views(DialogId dialog_id, const vector<MessageId> &message_ids) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto message_id : message_ids) {
auto *m = get_message(d, message_id);
if (m != nullptr) {
m->has_get_message_views_query = false;
m->need_view_counter_increment = false;
}
}
}
void MessagesManager::finish_get_message_extended_media(DialogId dialog_id, const vector<MessageId> &message_ids) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto message_id : message_ids) {
auto *m = get_message(d, message_id);
if (m != nullptr) {
m->has_get_extended_media_query = false;
}
}
}
Status MessagesManager::open_message_content(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "open_message_content");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
auto *m = get_message_force(d, full_message_id.get_message_id(), "open_message_content");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (m->message_id.is_scheduled() || m->message_id.is_yet_unsent() || m->is_outgoing) {
return Status::OK();
}
if (read_message_content(d, m, true, "open_message_content") &&
(m->message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat)) {
read_message_contents_on_server(dialog_id, {m->message_id}, 0, Auto());
}
if (m->content->get_type() == MessageContentType::LiveLocation) {
on_message_live_location_viewed(d, m);
}
return Status::OK();
}
class MessagesManager::ReadMessageContentsOnServerLogEvent {
public:
DialogId dialog_id_;
vector<MessageId> message_ids_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(message_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(message_ids_, parser);
}
};
uint64 MessagesManager::save_read_message_contents_on_server_log_event(DialogId dialog_id,
const vector<MessageId> &message_ids) {
ReadMessageContentsOnServerLogEvent log_event{dialog_id, message_ids};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadMessageContentsOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::read_message_contents_on_server(DialogId dialog_id, vector<MessageId> message_ids,
uint64 log_event_id, Promise<Unit> &&promise,
bool skip_log_event) {
CHECK(!message_ids.empty());
LOG(INFO) << "Read contents of " << format::as_array(message_ids) << " in " << dialog_id << " on server";
if (log_event_id == 0 && G()->parameters().use_message_db && !skip_log_event) {
log_event_id = save_read_message_contents_on_server_log_event(dialog_id, message_ids);
}
auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
promise = std::move(new_promise); // to prevent self-move
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
td_->create_handler<ReadMessagesContentsQuery>(std::move(promise))->send(std::move(message_ids));
break;
case DialogType::Channel:
td_->create_handler<ReadChannelMessagesContentsQuery>(std::move(promise))
->send(dialog_id.get_channel_id(), std::move(message_ids));
break;
case DialogType::SecretChat: {
CHECK(message_ids.size() == 1);
auto m = get_message_force({dialog_id, message_ids[0]}, "read_message_contents_on_server");
if (m != nullptr) {
send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_open_message,
dialog_id.get_secret_chat_id(), m->random_id, std::move(promise));
} else {
promise.set_error(Status::Error(400, "Message not found"));
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
}
void MessagesManager::click_animated_emoji_message(FullMessageId full_message_id,
Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "click_animated_emoji_message");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
auto message_id = get_persistent_message_id(d, full_message_id.get_message_id());
auto *m = get_message_force(d, message_id, "click_animated_emoji_message");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (m->message_id.is_scheduled() || dialog_id.get_type() != DialogType::User || !m->message_id.is_server()) {
return promise.set_value(nullptr);
}
get_message_content_animated_emoji_click_sticker(m->content.get(), full_message_id, td_, std::move(promise));
}
void MessagesManager::open_dialog(Dialog *d) {
CHECK(!td_->auth_manager_->is_bot());
DialogId dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
recently_opened_dialogs_.add_dialog(dialog_id);
if (d->is_opened) {
return;
}
d->is_opened = true;
d->was_opened = true;
auto min_message_id = MessageId(ServerMessageId(1));
if (d->last_message_id == MessageId() && d->last_read_outbox_message_id < min_message_id && d->messages != nullptr &&
d->messages->message_id < min_message_id) {
Message *m = d->messages.get();
while (m->right != nullptr) {
m = m->right.get();
}
if (m->message_id < min_message_id) {
read_history_inbox(dialog_id, m->message_id, -1, "open_dialog");
}
}
if (d->has_unload_timeout) {
LOG(INFO) << "Cancel unload timeout for " << dialog_id;
pending_unload_dialog_timeout_.cancel_timeout(dialog_id.get());
d->has_unload_timeout = false;
}
if (d->new_secret_chat_notification_id.is_valid()) {
remove_new_secret_chat_notification(d, true);
}
get_dialog_pinned_message(dialog_id, Auto());
if (d->active_group_call_id.is_valid()) {
td_->group_call_manager_->reload_group_call(d->active_group_call_id, Auto());
}
if (d->need_drop_default_send_message_as_dialog_id) {
CHECK(d->default_send_message_as_dialog_id.is_valid());
d->need_drop_default_send_message_as_dialog_id = false;
d->default_send_message_as_dialog_id = DialogId();
LOG(INFO) << "Set message sender in " << d->dialog_id << " to " << d->default_send_message_as_dialog_id;
on_dialog_updated(dialog_id, "open_dialog");
send_update_chat_message_sender(d);
}
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id());
reget_dialog_action_bar(dialog_id, "open_dialog", false);
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
if (!is_broadcast_channel(dialog_id)) {
auto participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id);
auto has_hidden_participants =
td_->contacts_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id());
if (participant_count < 195 && !has_hidden_participants) { // include unknown participant_count
td_->contacts_manager_->get_channel_participants(
channel_id, td_api::make_object<td_api::supergroupMembersFilterRecent>(), string(), 0, 200, 200, Auto());
}
}
get_channel_difference(dialog_id, d->pts, true, "open_dialog");
reget_dialog_action_bar(dialog_id, "open_dialog", false);
if (td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) {
auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id);
if (!linked_channel_id.is_valid()) {
// load linked_channel_id
send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false,
Promise<Unit>(), "open_dialog");
} else {
get_dialog_info_full(DialogId(linked_channel_id), Auto(), "open_dialog");
}
}
break;
}
case DialogType::SecretChat: {
// to repair dialog action bar
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (user_id.is_valid()) {
td_->contacts_manager_->reload_user_full(user_id, Promise<Unit>());
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
if (!td_->auth_manager_->is_bot()) {
auto online_count_it = dialog_online_member_counts_.find(dialog_id);
if (online_count_it != dialog_online_member_counts_.end()) {
auto &info = online_count_it->second;
CHECK(!info.is_update_sent);
if (Time::now() - info.update_time < ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME) {
info.is_update_sent = true;
send_update_chat_online_member_count(dialog_id, info.online_member_count);
}
}
if (d->has_scheduled_database_messages && !d->is_has_scheduled_database_messages_checked) {
CHECK(G()->parameters().use_message_db);
LOG(INFO) << "Send check has_scheduled_database_messages request";
d->is_has_scheduled_database_messages_checked = true;
G()->td_db()->get_message_db_async()->get_scheduled_messages(
dialog_id, 1,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](vector<MessageDbDialogMessage> messages) {
if (messages.empty()) {
send_closure(actor_id, &MessagesManager::set_dialog_has_scheduled_database_messages, dialog_id, false);
}
}));
}
}
}
void MessagesManager::close_dialog(Dialog *d) {
if (!d->is_opened) {
return;
}
d->is_opened = false;
auto dialog_id = d->dialog_id;
if (have_input_peer(dialog_id, AccessRights::Write)) {
if (pending_draft_message_timeout_.has_timeout(dialog_id.get())) {
pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), 0.0);
}
} else {
pending_draft_message_timeout_.cancel_timeout(dialog_id.get());
}
if (have_input_peer(dialog_id, AccessRights::Read)) {
if (pending_message_views_timeout_.has_timeout(dialog_id.get())) {
pending_message_views_timeout_.set_timeout_in(dialog_id.get(), 0.0);
}
if (pending_read_history_timeout_.has_timeout(dialog_id.get())) {
pending_read_history_timeout_.set_timeout_in(dialog_id.get(), 0.0);
}
} else {
pending_message_views_timeout_.cancel_timeout(dialog_id.get());
d->pending_viewed_message_ids.clear();
d->increment_view_counter = false;
pending_read_history_timeout_.cancel_timeout(dialog_id.get());
}
if (is_message_unload_enabled()) {
CHECK(!d->has_unload_timeout);
pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d));
d->has_unload_timeout = true;
}
dialog_viewed_messages_.erase(dialog_id);
update_viewed_messages_timeout_.cancel_timeout(dialog_id.get());
for (auto &it : d->pending_viewed_live_locations) {
auto live_location_task_id = it.second;
auto erased_count = viewed_live_location_tasks_.erase(live_location_task_id);
CHECK(erased_count > 0);
}
d->pending_viewed_live_locations.clear();
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
break;
case DialogType::Channel:
channel_get_difference_timeout_.cancel_timeout(dialog_id.get());
break;
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (!td_->auth_manager_->is_bot()) {
auto online_count_it = dialog_online_member_counts_.find(dialog_id);
if (online_count_it != dialog_online_member_counts_.end()) {
auto &info = online_count_it->second;
info.is_update_sent = false;
}
update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME);
}
}
td_api::object_ptr<td_api::ChatType> MessagesManager::get_chat_type_object(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return td_api::make_object<td_api::chatTypePrivate>(
td_->contacts_manager_->get_user_id_object(dialog_id.get_user_id(), "chatTypePrivate"));
case DialogType::Chat:
return td_api::make_object<td_api::chatTypeBasicGroup>(
td_->contacts_manager_->get_basic_group_id_object(dialog_id.get_chat_id(), "chatTypeBasicGroup"));
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
return td_api::make_object<td_api::chatTypeSupergroup>(
td_->contacts_manager_->get_supergroup_id_object(channel_id, "chatTypeSupergroup"),
!td_->contacts_manager_->is_megagroup_channel(channel_id));
}
case DialogType::SecretChat: {
auto secret_chat_id = dialog_id.get_secret_chat_id();
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
return td_api::make_object<td_api::chatTypeSecret>(
td_->contacts_manager_->get_secret_chat_id_object(secret_chat_id, "chatTypeSecret"),
td_->contacts_manager_->get_user_id_object(user_id, "chatTypeSecret"));
}
case DialogType::None:
default:
UNREACHABLE();
return nullptr;
}
}
td_api::object_ptr<td_api::ChatActionBar> MessagesManager::get_chat_action_bar_object(const Dialog *d) const {
CHECK(d != nullptr);
auto dialog_type = d->dialog_id.get_type();
if (dialog_type == DialogType::SecretChat) {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
if (!user_id.is_valid()) {
return nullptr;
}
const Dialog *user_d = get_dialog(DialogId(user_id));
if (user_d == nullptr || user_d->action_bar == nullptr) {
return nullptr;
}
return user_d->action_bar->get_chat_action_bar_object(DialogType::User, d->folder_id != FolderId::archive());
}
if (d->action_bar == nullptr) {
return nullptr;
}
return d->action_bar->get_chat_action_bar_object(dialog_type, false);
}
string MessagesManager::get_dialog_theme_name(const Dialog *d) const {
CHECK(d != nullptr);
if (d->dialog_id.get_type() == DialogType::SecretChat) {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
if (!user_id.is_valid()) {
return string();
}
d = get_dialog(DialogId(user_id));
if (d == nullptr) {
return string();
}
}
return d->theme_name;
}
td_api::object_ptr<td_api::chatJoinRequestsInfo> MessagesManager::get_chat_join_requests_info_object(
const Dialog *d) const {
if (d->pending_join_request_count == 0) {
return nullptr;
}
return td_api::make_object<td_api::chatJoinRequestsInfo>(
d->pending_join_request_count, td_->contacts_manager_->get_user_ids_object(d->pending_join_request_user_ids,
"get_chat_join_requests_info_object"));
}
td_api::object_ptr<td_api::videoChat> MessagesManager::get_video_chat_object(const Dialog *d) const {
auto active_group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, d->dialog_id);
auto default_participant_alias =
d->default_join_group_call_as_dialog_id.is_valid()
? get_message_sender_object_const(td_, d->default_join_group_call_as_dialog_id, "get_video_chat_object")
: nullptr;
return make_tl_object<td_api::videoChat>(active_group_call_id.get(),
active_group_call_id.is_valid() ? !d->is_group_call_empty : false,
std::move(default_participant_alias));
}
td_api::object_ptr<td_api::MessageSender> MessagesManager::get_default_message_sender_object(const Dialog *d) const {
auto as_dialog_id = d->default_send_message_as_dialog_id;
return as_dialog_id.is_valid()
? get_message_sender_object_const(td_, as_dialog_id, "get_default_message_sender_object")
: nullptr;
}
td_api::object_ptr<td_api::chat> MessagesManager::get_chat_object(const Dialog *d) const {
CHECK(d != nullptr);
auto chat_source = is_dialog_sponsored(d) ? sponsored_dialog_source_.get_chat_source_object() : nullptr;
auto can_delete = can_delete_dialog(d);
// TODO hide/show draft message when can_send_message(dialog_id) changes
auto draft_message = can_send_message(d->dialog_id).is_ok() ? get_draft_message_object(d->draft_message) : nullptr;
auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object();
return make_tl_object<td_api::chat>(
d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id),
get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)),
get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(),
get_message_object(d->dialog_id, get_message(d, d->last_message_id), "get_chat_object"),
get_chat_positions_object(d), get_default_message_sender_object(d),
get_dialog_has_protected_content(d->dialog_id), d->is_marked_as_unread, d->is_blocked,
get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_,
can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message,
d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(),
d->last_read_outbox_message_id.get(), d->unread_mention_count, d->unread_reaction_count,
get_chat_notification_settings_object(&d->notification_settings), std::move(available_reactions),
d->message_ttl.get_message_ttl_object(), get_dialog_theme_name(d), get_chat_action_bar_object(d),
get_video_chat_object(d), get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(),
std::move(draft_message), d->client_data);
}
tl_object_ptr<td_api::chat> MessagesManager::get_chat_object(DialogId dialog_id) const {
return get_chat_object(get_dialog(dialog_id));
}
tl_object_ptr<td_api::chats> MessagesManager::get_chats_object(int32 total_count, const vector<DialogId> &dialog_ids) {
if (total_count == -1) {
total_count = narrow_cast<int32>(dialog_ids.size());
}
return td_api::make_object<td_api::chats>(total_count,
transform(dialog_ids, [](DialogId dialog_id) { return dialog_id.get(); }));
}
tl_object_ptr<td_api::chats> MessagesManager::get_chats_object(const std::pair<int32, vector<DialogId>> &dialog_ids) {
return get_chats_object(dialog_ids.first, dialog_ids.second);
}
td_api::object_ptr<td_api::chatFilter> MessagesManager::get_chat_filter_object(DialogFilterId dialog_filter_id) {
CHECK(!td_->auth_manager_->is_bot());
auto filter = get_dialog_filter(dialog_filter_id);
if (filter == nullptr) {
return nullptr;
}
return get_chat_filter_object(filter);
}
td_api::object_ptr<td_api::chatFilter> MessagesManager::get_chat_filter_object(const DialogFilter *filter) {
vector<DialogId> left_dialog_ids;
auto get_chat_ids = [this, dialog_filter_id = filter->dialog_filter_id,
&left_dialog_ids](const vector<InputDialogId> &input_dialog_ids) {
vector<int64> chat_ids;
chat_ids.reserve(input_dialog_ids.size());
for (auto &input_dialog_id : input_dialog_ids) {
auto dialog_id = input_dialog_id.get_dialog_id();
const Dialog *d = get_dialog(dialog_id);
if (d != nullptr) {
if (d->order != DEFAULT_ORDER) {
chat_ids.push_back(dialog_id.get());
} else {
LOG(INFO) << "Skip nonjoined " << dialog_id << " from " << dialog_filter_id;
left_dialog_ids.push_back(dialog_id);
}
} else {
LOG(ERROR) << "Can't find " << dialog_id << " from " << dialog_filter_id;
}
}
return chat_ids;
};
auto result = td_api::make_object<td_api::chatFilter>(
filter->title, filter->get_icon_name(), get_chat_ids(filter->pinned_dialog_ids),
get_chat_ids(filter->included_dialog_ids), get_chat_ids(filter->excluded_dialog_ids), filter->exclude_muted,
filter->exclude_read, filter->exclude_archived, filter->include_contacts, filter->include_non_contacts,
filter->include_bots, filter->include_groups, filter->include_channels);
delete_dialogs_from_filter(filter, std::move(left_dialog_ids), "get_chat_filter_object");
return result;
}
std::pair<bool, int32> MessagesManager::get_dialog_mute_until(DialogId dialog_id, const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
if (d == nullptr || !d->notification_settings.is_synchronized) {
auto scope = get_dialog_notification_setting_scope(dialog_id);
return {false, td_->notification_settings_manager_->get_scope_mute_until(scope)};
}
return {d->notification_settings.is_use_default_fixed, get_dialog_mute_until(d)};
}
int64 MessagesManager::get_dialog_notification_ringtone_id(DialogId dialog_id, const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
if (d == nullptr || !d->notification_settings.is_synchronized || d->notification_settings.use_default_mute_until) {
auto scope = get_dialog_notification_setting_scope(dialog_id);
return get_notification_sound_ringtone_id(td_->notification_settings_manager_->get_scope_notification_sound(scope));
}
return get_notification_sound_ringtone_id(d->notification_settings.sound);
}
NotificationSettingsScope MessagesManager::get_dialog_notification_setting_scope(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
return NotificationSettingsScope::Private;
case DialogType::Chat:
return NotificationSettingsScope::Group;
case DialogType::Channel:
return is_broadcast_channel(dialog_id) ? NotificationSettingsScope::Channel : NotificationSettingsScope::Group;
case DialogType::None:
default:
UNREACHABLE();
return NotificationSettingsScope::Private;
}
}
vector<DialogId> MessagesManager::get_dialog_notification_settings_exceptions(NotificationSettingsScope scope,
bool filter_scope, bool compare_sound,
bool force, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
bool have_all_dialogs = true;
for (const auto &list : dialog_folders_) {
if (list.second.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
have_all_dialogs = false;
}
}
if (have_all_dialogs || force) {
vector<DialogDate> ordered_dialogs;
auto my_dialog_id = get_my_dialog_id();
for (const auto &list : dialog_folders_) {
for (const auto &dialog_date : list.second.ordered_dialogs_) {
auto dialog_id = dialog_date.get_dialog_id();
if (filter_scope && get_dialog_notification_setting_scope(dialog_id) != scope) {
continue;
}
if (dialog_id == my_dialog_id) {
continue;
}
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
LOG_CHECK(d->folder_id == list.first)
<< list.first << ' ' << dialog_id << ' ' << d->folder_id << ' ' << d->order;
if (d->order == DEFAULT_ORDER) {
break;
}
if (are_default_dialog_notification_settings(d->notification_settings, compare_sound)) {
continue;
}
if (is_dialog_message_notification_disabled(dialog_id, std::numeric_limits<int32>::max())) {
continue;
}
ordered_dialogs.push_back(DialogDate(get_dialog_base_order(d), dialog_id));
}
}
std::sort(ordered_dialogs.begin(), ordered_dialogs.end());
vector<DialogId> result;
for (auto &dialog_date : ordered_dialogs) {
CHECK(result.empty() || result.back() != dialog_date.get_dialog_id());
result.push_back(dialog_date.get_dialog_id());
}
promise.set_value(Unit());
return result;
}
for (const auto &folder : dialog_folders_) {
load_folder_dialog_list(folder.first, MAX_GET_DIALOGS, true);
}
td_->notification_settings_manager_->get_notify_settings_exceptions(scope, filter_scope, compare_sound,
std::move(promise));
return {};
}
DialogNotificationSettings *MessagesManager::get_dialog_notification_settings(DialogId dialog_id, bool force) {
Dialog *d = get_dialog_force(dialog_id, "get_dialog_notification_settings");
if (d == nullptr) {
return nullptr;
}
if (!force && !have_input_peer(dialog_id, AccessRights::Read)) {
return nullptr;
}
return &d->notification_settings;
}
Status MessagesManager::set_dialog_notification_settings(
DialogId dialog_id, tl_object_ptr<td_api::chatNotificationSettings> &&notification_settings) {
CHECK(!td_->auth_manager_->is_bot());
auto current_settings = get_dialog_notification_settings(dialog_id, false);
if (current_settings == nullptr) {
return Status::Error(400, "Wrong chat identifier specified");
}
if (dialog_id == get_my_dialog_id()) {
return Status::Error(400, "Notification settings of the Saved Messages chat can't be changed");
}
TRY_RESULT(new_settings, ::td::get_dialog_notification_settings(std::move(notification_settings), current_settings));
if (update_dialog_notification_settings(dialog_id, current_settings, std::move(new_settings))) {
update_dialog_notification_settings_on_server(dialog_id, false);
}
return Status::OK();
}
void MessagesManager::reset_all_notification_settings() {
CHECK(!td_->auth_manager_->is_bot());
dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
DialogNotificationSettings new_dialog_settings;
new_dialog_settings.is_synchronized = true;
Dialog *d = dialog.get();
update_dialog_notification_settings(dialog_id, &d->notification_settings, std::move(new_dialog_settings));
});
td_->notification_settings_manager_->reset_scope_notification_settings();
reset_all_notification_settings_on_server(0);
}
class MessagesManager::ResetAllNotificationSettingsOnServerLogEvent {
public:
template <class StorerT>
void store(StorerT &storer) const {
}
template <class ParserT>
void parse(ParserT &parser) {
}
};
uint64 MessagesManager::save_reset_all_notification_settings_on_server_log_event() {
ResetAllNotificationSettingsOnServerLogEvent log_event;
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ResetAllNotificationSettingsOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::reset_all_notification_settings_on_server(uint64 log_event_id) {
CHECK(!td_->auth_manager_->is_bot());
if (log_event_id == 0) {
log_event_id = save_reset_all_notification_settings_on_server_log_event();
}
LOG(INFO) << "Reset all notification settings";
td_->notification_settings_manager_->reset_notify_settings(get_erase_log_event_promise(log_event_id));
}
tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dialog_id, MessageId from_message_id,
int32 offset, int32 limit, int left_tries,
bool only_local, Promise<Unit> &&promise) {
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return nullptr;
}
if (limit > MAX_GET_HISTORY) {
limit = MAX_GET_HISTORY;
}
if (offset > 0) {
promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
return nullptr;
}
if (offset <= -MAX_GET_HISTORY) {
promise.set_error(Status::Error(400, "Parameter offset must be greater than -100"));
return nullptr;
}
if (offset < -limit) {
promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit"));
return nullptr;
}
bool is_limit_increased = false;
if (limit == -offset) {
limit++;
is_limit_increased = true;
}
CHECK(0 < limit && limit <= MAX_GET_HISTORY);
CHECK(-limit < offset && offset <= 0);
if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid()) {
promise.set_error(Status::Error(400, "Invalid value of parameter from_message_id specified"));
return nullptr;
}
const Dialog *d = get_dialog_force(dialog_id, "get_dialog_history");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return nullptr;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return nullptr;
}
LOG(INFO) << "Get " << (only_local ? "local " : "") << "history in " << dialog_id << " from " << from_message_id
<< " with offset " << offset << " and limit " << limit << ", " << left_tries
<< " tries left. Last read inbox message is " << d->last_read_inbox_message_id
<< ", last read outbox message is " << d->last_read_outbox_message_id
<< ", have_full_history = " << d->have_full_history
<< ", have_full_history_source = " << d->have_full_history_source;
MessagesConstIterator p(d, from_message_id);
LOG(DEBUG) << "Iterator points to " << (*p ? (*p)->message_id : MessageId());
bool from_the_end = (d->last_message_id != MessageId() && from_message_id > d->last_message_id) ||
from_message_id >= MessageId::max();
if (from_the_end) {
limit += offset;
offset = 0;
if (d->last_message_id == MessageId()) {
p = MessagesConstIterator();
}
} else {
bool have_a_gap = false;
if (*p == nullptr) {
// there is no gap if from_message_id is less than first message in the dialog
if (left_tries == 0 && d->messages != nullptr && offset < 0) {
const Message *cur = d->messages.get();
while (cur->left != nullptr) {
cur = cur->left.get();
}
CHECK(cur->message_id > from_message_id);
from_message_id = cur->message_id;
p = MessagesConstIterator(d, from_message_id);
} else {
have_a_gap = true;
}
} else if ((*p)->message_id != from_message_id) {
CHECK((*p)->message_id < from_message_id);
if (!(*p)->have_next && (d->last_message_id == MessageId() || (*p)->message_id < d->last_message_id)) {
have_a_gap = true;
}
}
if (have_a_gap) {
LOG(INFO) << "Have a gap near message to get chat history from";
p = MessagesConstIterator();
}
if (*p != nullptr && (*p)->message_id == from_message_id) {
if (offset < 0) {
offset++;
} else {
--p;
}
}
while (*p != nullptr && offset < 0) {
++p;
if (*p) {
++offset;
from_message_id = (*p)->message_id;
}
}
if (offset < 0 && ((d->last_message_id != MessageId() && from_message_id >= d->last_message_id) ||
(!have_a_gap && left_tries == 0))) {
CHECK(!have_a_gap);
limit += offset;
offset = 0;
p = MessagesConstIterator(d, from_message_id);
}
if (!have_a_gap && offset < 0) {
offset--;
}
}
LOG(INFO) << "Iterator after applying offset points to " << (*p ? (*p)->message_id : MessageId())
<< ", offset = " << offset << ", limit = " << limit << ", from_the_end = " << from_the_end;
vector<tl_object_ptr<td_api::message>> messages;
if (*p != nullptr && offset == 0) {
while (*p != nullptr && messages.size() < static_cast<size_t>(limit)) {
messages.push_back(get_message_object(dialog_id, *p, "get_dialog_history"));
from_message_id = (*p)->message_id;
from_the_end = false;
--p;
}
}
if (!messages.empty()) {
// maybe need some messages
CHECK(offset == 0);
preload_newer_messages(d, MessageId(messages[0]->id_));
preload_older_messages(d, MessageId(messages.back()->id_));
} else if (messages.size() < static_cast<size_t>(limit) && left_tries != 0 &&
!(d->is_empty && d->have_full_history && left_tries < 3)) {
// there can be more messages in the database or on the server, need to load them
if (from_the_end) {
from_message_id = MessageId();
}
send_closure_later(actor_id(this), &MessagesManager::load_messages, dialog_id, from_message_id, offset,
limit - static_cast<int32>(messages.size()), left_tries, only_local, std::move(promise));
return nullptr;
}
LOG(INFO) << "Have " << messages.size() << " messages out of requested "
<< (is_limit_increased ? "increased " : "exact ") << limit;
if (is_limit_increased && static_cast<size_t>(limit) == messages.size()) {
messages.pop_back();
}
LOG(INFO) << "Return " << messages.size() << " messages in result to getChatHistory";
promise.set_value(Unit()); // can return some messages
return get_messages_object(-1, std::move(messages), false); // TODO return real total_count of messages in the dialog
}
class MessagesManager::ReadHistoryOnServerLogEvent {
public:
DialogId dialog_id_;
MessageId max_message_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(max_message_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(max_message_id_, parser);
}
};
class MessagesManager::ReadHistoryInSecretChatLogEvent {
public:
DialogId dialog_id_;
int32 max_date_ = 0;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(max_date_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(max_date_, parser);
}
};
class MessagesManager::ReadMessageThreadHistoryOnServerLogEvent {
public:
DialogId dialog_id_;
MessageId top_thread_message_id_;
MessageId max_message_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(top_thread_message_id_, storer);
td::store(max_message_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(top_thread_message_id_, parser);
td::parse(max_message_id_, parser);
}
};
void MessagesManager::read_history_on_server(Dialog *d, MessageId max_message_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
CHECK(!max_message_id.is_scheduled());
auto dialog_id = d->dialog_id;
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
bool need_delay = d->is_opened && !is_secret &&
(d->server_unread_count > 0 || (!need_unread_counter(d->order) && d->last_message_id.is_valid() &&
max_message_id < d->last_message_id));
LOG(INFO) << "Read history in " << dialog_id << " on server up to " << max_message_id << " with"
<< (need_delay ? "" : "out") << " delay";
if (is_secret) {
auto *m = get_message_force(d, max_message_id, "read_history_on_server");
if (m == nullptr) {
LOG(ERROR) << "Failed to read history in " << dialog_id << " up to " << max_message_id;
return;
}
ReadHistoryInSecretChatLogEvent log_event;
log_event.dialog_id_ = dialog_id;
log_event.max_date_ = m->date;
add_log_event(d->read_history_log_event_ids[0], get_log_event_storer(log_event),
LogEvent::HandlerType::ReadHistoryInSecretChat, "read history");
d->last_read_inbox_message_date = m->date;
} else if (G()->parameters().use_message_db) {
ReadHistoryOnServerLogEvent log_event;
log_event.dialog_id_ = dialog_id;
log_event.max_message_id_ = max_message_id;
add_log_event(d->read_history_log_event_ids[0], get_log_event_storer(log_event),
LogEvent::HandlerType::ReadHistoryOnServer, "read history");
}
d->updated_read_history_message_ids.insert(MessageId());
pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0);
}
void MessagesManager::read_message_thread_history_on_server(Dialog *d, MessageId top_thread_message_id,
MessageId max_message_id, MessageId last_message_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
CHECK(top_thread_message_id.is_valid());
CHECK(top_thread_message_id.is_server());
CHECK(max_message_id.is_server());
auto dialog_id = d->dialog_id;
LOG(INFO) << "Read history in thread of " << top_thread_message_id << " in " << dialog_id << " on server up to "
<< max_message_id;
if (G()->parameters().use_message_db) {
ReadMessageThreadHistoryOnServerLogEvent log_event;
log_event.dialog_id_ = dialog_id;
log_event.top_thread_message_id_ = top_thread_message_id;
log_event.max_message_id_ = max_message_id;
add_log_event(d->read_history_log_event_ids[top_thread_message_id.get()], get_log_event_storer(log_event),
LogEvent::HandlerType::ReadMessageThreadHistoryOnServer, "read history");
}
d->updated_read_history_message_ids.insert(top_thread_message_id);
bool need_delay = d->is_opened && last_message_id.is_valid() && max_message_id < last_message_id;
pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0);
}
void MessagesManager::do_read_history_on_server(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto top_thread_message_id : d->updated_read_history_message_ids) {
if (!top_thread_message_id.is_valid()) {
read_history_on_server_impl(d, MessageId());
} else {
read_message_thread_history_on_server_impl(d, top_thread_message_id, MessageId());
}
}
reset_to_empty(d->updated_read_history_message_ids);
}
void MessagesManager::read_history_on_server_impl(Dialog *d, MessageId max_message_id) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
{
auto message_id = d->last_read_inbox_message_id;
if (dialog_id.get_type() != DialogType::SecretChat) {
message_id = message_id.get_prev_server_message_id();
}
if (message_id > max_message_id) {
max_message_id = message_id;
}
}
Promise<Unit> promise;
if (d->read_history_log_event_ids[0].log_event_id != 0) {
d->read_history_log_event_ids[0].generation++;
promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
generation = d->read_history_log_event_ids[0].generation](Result<Unit> result) {
if (!G()->close_flag()) {
send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, MessageId(), generation);
}
});
}
if (d->need_repair_server_unread_count && need_unread_counter(d->order)) {
repair_server_unread_count(dialog_id, d->server_unread_count, "read_history_on_server_impl");
}
if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_value(Unit());
}
LOG(INFO) << "Send read history request in " << dialog_id << " up to " << max_message_id;
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
td_->create_handler<ReadHistoryQuery>(std::move(promise))->send(dialog_id, max_message_id);
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
td_->create_handler<ReadChannelHistoryQuery>(std::move(promise))->send(channel_id, max_message_id);
break;
}
case DialogType::SecretChat: {
auto secret_chat_id = dialog_id.get_secret_chat_id();
auto date = d->last_read_inbox_message_date;
auto *m = get_message_force(d, max_message_id, "read_history_on_server_impl");
if (m != nullptr && m->date > date) {
date = m->date;
}
if (date == 0) {
LOG(ERROR) << "Don't know last read inbox message date in " << dialog_id;
return promise.set_value(Unit());
}
send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_read_history, secret_chat_id, date,
std::move(promise));
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
}
void MessagesManager::read_message_thread_history_on_server_impl(Dialog *d, MessageId top_thread_message_id,
MessageId max_message_id) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
CHECK(dialog_id.get_type() == DialogType::Channel);
const Message *m = get_message_force(d, top_thread_message_id, "read_message_thread_history_on_server_impl");
if (m != nullptr) {
auto message_id = m->reply_info.last_read_inbox_message_id_.get_prev_server_message_id();
if (message_id > max_message_id) {
max_message_id = message_id;
}
}
Promise<Unit> promise;
if (d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id != 0) {
d->read_history_log_event_ids[top_thread_message_id.get()].generation++;
promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_id, top_thread_message_id,
generation = d->read_history_log_event_ids[top_thread_message_id.get()].generation](Result<Unit> result) {
if (!G()->close_flag()) {
send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, top_thread_message_id,
generation);
}
});
}
if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_value(Unit());
}
LOG(INFO) << "Send read history request in thread of " << top_thread_message_id << " in " << dialog_id << " up to "
<< max_message_id;
td_->create_handler<ReadDiscussionQuery>(std::move(promise))->send(dialog_id, top_thread_message_id, max_message_id);
}
void MessagesManager::on_read_history_finished(DialogId dialog_id, MessageId top_thread_message_id, uint64 generation) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto it = d->read_history_log_event_ids.find(top_thread_message_id.get());
if (it == d->read_history_log_event_ids.end()) {
return;
}
delete_log_event(it->second, generation, "read history");
if (it->second.log_event_id == 0) {
d->read_history_log_event_ids.erase(it);
}
}
template <class T, class It>
vector<MessageId> MessagesManager::get_message_history_slice(const T &begin, It it, const T &end,
MessageId from_message_id, int32 offset, int32 limit) {
int32 left_offset = -offset;
int32 left_limit = limit + offset;
while (left_offset > 0 && it != end) {
++it;
left_offset--;
left_limit++;
}
vector<MessageId> message_ids;
while (left_limit > 0 && it != begin) {
--it;
left_limit--;
message_ids.push_back(*it);
}
return message_ids;
}
std::pair<DialogId, vector<MessageId>> MessagesManager::get_message_thread_history(
DialogId dialog_id, MessageId message_id, MessageId from_message_id, int32 offset, int32 limit, int64 &random_id,
Promise<Unit> &&promise) {
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
if (limit > MAX_GET_HISTORY) {
limit = MAX_GET_HISTORY;
}
if (offset > 0) {
promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
return {};
}
if (offset <= -MAX_GET_HISTORY) {
promise.set_error(Status::Error(400, "Parameter offset must be greater than -100"));
return {};
}
if (offset < -limit) {
promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit"));
return {};
}
bool is_limit_increased = false;
if (limit == -offset) {
limit++;
is_limit_increased = true;
}
CHECK(0 < limit && limit <= MAX_GET_HISTORY);
CHECK(-limit < offset && offset <= 0);
Dialog *d = get_dialog_force(dialog_id, "get_message_thread_history");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return {};
}
if (dialog_id.get_type() != DialogType::Channel) {
promise.set_error(Status::Error(400, "Can't get message thread history in the chat"));
return {};
}
if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid()) {
promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return {};
}
FullMessageId top_thread_full_message_id;
if (message_id == MessageId(ServerMessageId(1)) && is_forum_channel(dialog_id)) {
top_thread_full_message_id = FullMessageId{dialog_id, message_id};
} else {
message_id = get_persistent_message_id(d, message_id);
Message *m = get_message_force(d, message_id, "get_message_thread_history 1");
if (m == nullptr) {
promise.set_error(Status::Error(400, "Message not found"));
return {};
}
auto r_top_thread_full_message_id = get_top_thread_full_message_id(dialog_id, m, true);
if (r_top_thread_full_message_id.is_error()) {
promise.set_error(r_top_thread_full_message_id.move_as_error());
return {};
}
top_thread_full_message_id = r_top_thread_full_message_id.move_as_ok();
if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) &&
top_thread_full_message_id.get_message_id() != m->message_id) {
CHECK(dialog_id == top_thread_full_message_id.get_dialog_id());
// get information about the thread from the top message
message_id = top_thread_full_message_id.get_message_id();
CHECK(message_id.is_valid());
}
if (!top_thread_full_message_id.get_message_id().is_valid()) {
CHECK(m->reply_info.is_comment_);
get_message_thread(
dialog_id, message_id,
PromiseCreator::lambda([promise = std::move(promise)](Result<MessageThreadInfo> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(Unit());
}
}));
return {};
}
}
if (random_id != 0) {
// request has already been sent before
auto it = found_dialog_messages_.find(random_id);
CHECK(it != found_dialog_messages_.end());
auto result = std::move(it->second.second);
found_dialog_messages_.erase(it);
auto dialog_id_it = found_dialog_messages_dialog_id_.find(random_id);
if (dialog_id_it != found_dialog_messages_dialog_id_.end()) {
dialog_id = dialog_id_it->second;
found_dialog_messages_dialog_id_.erase(dialog_id_it);
d = get_dialog(dialog_id);
CHECK(d != nullptr);
}
if (dialog_id != top_thread_full_message_id.get_dialog_id()) {
promise.set_error(Status::Error(500, "Receive messages in an unexpected chat"));
return {};
}
auto yet_unsent_it = d->yet_unsent_thread_message_ids.find(top_thread_full_message_id.get_message_id());
if (yet_unsent_it != d->yet_unsent_thread_message_ids.end()) {
const std::set<MessageId> &message_ids = yet_unsent_it->second;
auto merge_message_ids = get_message_history_slice(message_ids.begin(), message_ids.lower_bound(from_message_id),
message_ids.end(), from_message_id, offset, limit);
vector<MessageId> new_result(result.size() + merge_message_ids.size());
std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(),
std::greater<>());
result = std::move(new_result);
}
Message *top_m = get_message_force(d, top_thread_full_message_id.get_message_id(), "get_message_thread_history 2");
if (top_m != nullptr && !top_m->local_thread_message_ids.empty()) {
vector<MessageId> &message_ids = top_m->local_thread_message_ids;
vector<MessageId> merge_message_ids;
while (true) {
merge_message_ids = get_message_history_slice(
message_ids.begin(), std::lower_bound(message_ids.begin(), message_ids.end(), from_message_id),
message_ids.end(), from_message_id, offset, limit);
bool found_deleted = false;
for (auto local_message_id : merge_message_ids) {
Message *local_m = get_message_force(d, local_message_id, "get_message_thread_history 3");
if (local_m == nullptr) {
auto local_it = std::lower_bound(message_ids.begin(), message_ids.end(), local_message_id);
CHECK(local_it != message_ids.end() && *local_it == local_message_id);
message_ids.erase(local_it);
found_deleted = true;
}
}
if (!found_deleted) {
break;
}
on_message_changed(d, top_m, false, "get_message_thread_history");
}
vector<MessageId> new_result(result.size() + merge_message_ids.size());
std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(),
std::greater<>());
result = std::move(new_result);
}
if (is_limit_increased) {
limit--;
}
std::reverse(result.begin(), result.end());
result = get_message_history_slice(result.begin(), std::lower_bound(result.begin(), result.end(), from_message_id),
result.end(), from_message_id, offset, limit);
LOG(INFO) << "Return " << result.size() << " messages in result to getMessageThreadHistory";
promise.set_value(Unit());
return {dialog_id, std::move(result)};
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_dialog_messages_.count(random_id) > 0);
found_dialog_messages_[random_id]; // reserve place for result
td_->create_handler<SearchMessagesQuery>(std::move(promise))
->send(dialog_id, string(), DialogId(), from_message_id.get_next_server_message_id(), offset, limit,
MessageSearchFilter::Empty, message_id, random_id);
return {};
}
td_api::object_ptr<td_api::messageCalendar> MessagesManager::get_dialog_message_calendar(DialogId dialog_id,
MessageId from_message_id,
MessageSearchFilter filter,
int64 &random_id, bool use_db,
Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_dialog_message_calendars_.find(random_id);
if (it != found_dialog_message_calendars_.end()) {
auto result = std::move(it->second);
found_dialog_message_calendars_.erase(it);
promise.set_value(Unit());
return result;
}
random_id = 0;
}
LOG(INFO) << "Get message calendar in " << dialog_id << " filtered by " << filter << " from " << from_message_id;
if (from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid() && from_message_id != MessageId()) {
promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return {};
}
from_message_id = from_message_id.get_next_server_message_id();
const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_calendar");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return {};
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_dialog_message_calendars_.count(random_id) > 0);
found_dialog_message_calendars_[random_id]; // reserve place for result
CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention ||
filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) {
promise.set_error(Status::Error(400, "The filter is not supported"));
return {};
}
// Trying to use database
if (use_db && G()->parameters().use_message_db) {
MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter);
int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
auto fixed_from_message_id = from_message_id;
if (fixed_from_message_id == MessageId()) {
fixed_from_message_id = MessageId::max();
}
LOG(INFO) << "Get message calendar in " << dialog_id << " from " << fixed_from_message_id << ", have up to "
<< first_db_message_id << ", message_count = " << message_count;
if (first_db_message_id < fixed_from_message_id && message_count != -1) {
LOG(INFO) << "Get message calendar from database in " << dialog_id << " from " << fixed_from_message_id;
auto new_promise =
PromiseCreator::lambda([random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter,
promise = std::move(promise)](Result<MessageDbCalendar> r_calendar) mutable {
send_closure(G()->messages_manager(), &MessagesManager::on_get_message_calendar_from_database, random_id,
dialog_id, fixed_from_message_id, first_db_message_id, filter, std::move(r_calendar),
std::move(promise));
});
MessageDbDialogCalendarQuery db_query;
db_query.dialog_id = dialog_id;
db_query.filter = filter;
db_query.from_message_id = fixed_from_message_id;
db_query.tz_offset = static_cast<int32>(td_->option_manager_->get_option_integer("utc_time_offset"));
G()->td_db()->get_message_db_async()->get_dialog_message_calendar(db_query, std::move(new_promise));
return {};
}
}
if (filter == MessageSearchFilter::FailedToSend) {
promise.set_value(Unit());
return {};
}
LOG(DEBUG) << "Get message calendar from server in " << dialog_id << " from " << from_message_id;
switch (dialog_id.get_type()) {
case DialogType::None:
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
td_->create_handler<GetSearchResultCalendarQuery>(std::move(promise))
->send(dialog_id, from_message_id, filter, random_id);
break;
case DialogType::SecretChat:
promise.set_value(Unit());
break;
default:
UNREACHABLE();
promise.set_error(Status::Error(500, "Search messages is not supported"));
}
return {};
}
void MessagesManager::on_get_message_calendar_from_database(int64 random_id, DialogId dialog_id,
MessageId from_message_id, MessageId first_db_message_id,
MessageSearchFilter filter,
Result<MessageDbCalendar> r_calendar,
Promise<Unit> promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (r_calendar.is_error()) {
LOG(ERROR) << "Failed to get message calendar from the database: " << r_calendar.error();
if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat &&
filter != MessageSearchFilter::FailedToSend) {
found_dialog_message_calendars_.erase(random_id);
}
return promise.set_value(Unit());
}
CHECK(!from_message_id.is_scheduled());
CHECK(!first_db_message_id.is_scheduled());
auto calendar = r_calendar.move_as_ok();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto it = found_dialog_message_calendars_.find(random_id);
CHECK(it != found_dialog_message_calendars_.end());
CHECK(it->second == nullptr);
vector<std::pair<MessageId, int32>> periods;
periods.reserve(calendar.messages.size());
for (size_t i = 0; i < calendar.messages.size(); i++) {
auto m = on_get_message_from_database(d, calendar.messages[i], false, "on_get_message_calendar_from_database");
if (m != nullptr && first_db_message_id <= m->message_id) {
CHECK(!m->message_id.is_scheduled());
periods.emplace_back(m->message_id, calendar.total_counts[i]);
}
}
if (periods.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) {
LOG(INFO) << "No messages found in database";
found_dialog_message_calendars_.erase(it);
} else {
auto total_count = d->message_count_by_index[message_search_filter_index(filter)];
vector<td_api::object_ptr<td_api::messageCalendarDay>> days;
for (auto &period : periods) {
const auto *m = get_message(d, period.first);
CHECK(m != nullptr);
days.push_back(td_api::make_object<td_api::messageCalendarDay>(
period.second, get_message_object(dialog_id, m, "on_get_message_calendar_from_database")));
}
it->second = td_api::make_object<td_api::messageCalendar>(total_count, std::move(days));
}
promise.set_value(Unit());
}
std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
DialogId dialog_id, const string &query, const td_api::object_ptr<td_api::MessageSender> &sender,
MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id,
int64 &random_id, bool use_db, Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_dialog_messages_.find(random_id);
if (it != found_dialog_messages_.end()) {
CHECK(found_dialog_messages_dialog_id_.count(random_id) == 0);
auto result = std::move(it->second);
found_dialog_messages_.erase(it);
promise.set_value(Unit());
return result;
}
random_id = 0;
}
LOG(INFO) << "Search messages with query \"" << query << "\" in " << dialog_id << " sent by "
<< oneline(to_string(sender)) << " in thread of " << top_thread_message_id << " filtered by " << filter
<< " from " << from_message_id << " with offset " << offset << " and limit " << limit;
std::pair<int32, vector<MessageId>> result;
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return result;
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
if (limit <= -offset) {
promise.set_error(Status::Error(400, "Parameter limit must be greater than -offset"));
return result;
}
if (offset > 0) {
promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
return result;
}
if (from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid() && from_message_id != MessageId()) {
promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return result;
}
from_message_id = from_message_id.get_next_server_message_id();
const Dialog *d = get_dialog_force(dialog_id, "search_dialog_messages");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return result;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return result;
}
auto r_sender_dialog_id = get_message_sender_dialog_id(td_, sender, true, true);
if (r_sender_dialog_id.is_error()) {
promise.set_error(r_sender_dialog_id.move_as_error());
return result;
}
auto sender_dialog_id = r_sender_dialog_id.move_as_ok();
if (sender_dialog_id != DialogId() && !have_input_peer(sender_dialog_id, AccessRights::Know)) {
promise.set_error(Status::Error(400, "Invalid message sender specified"));
return result;
}
if (sender_dialog_id == dialog_id && is_broadcast_channel(dialog_id)) {
sender_dialog_id = DialogId();
}
if (filter == MessageSearchFilter::FailedToSend && sender_dialog_id.is_valid()) {
if (sender_dialog_id != get_my_dialog_id()) {
promise.set_value(Unit());
return result;
}
sender_dialog_id = DialogId();
}
if (top_thread_message_id != MessageId()) {
if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
promise.set_error(Status::Error(400, "Invalid message thread ID specified"));
return result;
}
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
promise.set_error(Status::Error(400, "Can't filter by message thread ID in the chat"));
return result;
}
}
if (sender_dialog_id.get_type() == DialogType::SecretChat) {
promise.set_value(Unit());
return result;
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_dialog_messages_.count(random_id) > 0);
found_dialog_messages_[random_id]; // reserve place for result
if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) {
if (!query.empty()) {
promise.set_error(Status::Error(400, "Non-empty query is unsupported with the specified filter"));
return result;
}
if (sender_dialog_id.is_valid()) {
promise.set_error(Status::Error(400, "Filtering by sender is unsupported with the specified filter"));
return result;
}
}
// Trying to use database
if (use_db && query.empty() && G()->parameters().use_message_db && filter != MessageSearchFilter::Empty &&
!sender_dialog_id.is_valid() && top_thread_message_id == MessageId()) {
MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter);
int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
auto fixed_from_message_id = from_message_id;
if (fixed_from_message_id == MessageId()) {
fixed_from_message_id = MessageId::max();
}
LOG(INFO) << "Search messages in " << dialog_id << " from " << fixed_from_message_id << ", have up to "
<< first_db_message_id << ", message_count = " << message_count;
if ((first_db_message_id < fixed_from_message_id || (first_db_message_id == fixed_from_message_id && offset < 0)) &&
message_count != -1) {
LOG(INFO) << "Search messages in database in " << dialog_id << " from " << fixed_from_message_id
<< " and with limit " << limit;
auto new_promise = PromiseCreator::lambda(
[random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit,
promise = std::move(promise)](Result<vector<MessageDbDialogMessage>> r_messages) mutable {
send_closure(G()->messages_manager(), &MessagesManager::on_search_dialog_message_db_result, random_id,
dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit,
std::move(r_messages), std::move(promise));
});
MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.filter = filter;
db_query.from_message_id = fixed_from_message_id;
db_query.offset = offset;
db_query.limit = limit;
G()->td_db()->get_message_db_async()->get_messages(db_query, std::move(new_promise));
return result;
}
}
if (filter == MessageSearchFilter::FailedToSend) {
promise.set_value(Unit());
return result;
}
LOG(DEBUG) << "Search messages on server in " << dialog_id << " with query \"" << query << "\" from "
<< sender_dialog_id << " in thread of " << top_thread_message_id << " from " << from_message_id
<< " and with limit " << limit;
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
td_->create_handler<SearchMessagesQuery>(std::move(promise))
->send(dialog_id, query, sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id,
random_id);
break;
case DialogType::SecretChat:
if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::Pinned ||
filter == MessageSearchFilter::UnreadReaction) {
promise.set_value(Unit());
} else {
promise.set_error(Status::Error(500, "Search messages in secret chats is not supported"));
}
break;
case DialogType::None:
default:
UNREACHABLE();
promise.set_error(Status::Error(500, "Search messages is not supported"));
}
return result;
}
std::pair<int32, vector<FullMessageId>> MessagesManager::search_call_messages(MessageId from_message_id, int32 limit,
bool only_missed, int64 &random_id,
bool use_db, Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_call_messages_.find(random_id);
if (it != found_call_messages_.end()) {
auto result = std::move(it->second);
found_call_messages_.erase(it);
promise.set_value(Unit());
return result;
}
random_id = 0;
}
LOG(INFO) << "Search call messages from " << from_message_id << " with limit " << limit;
std::pair<int32, vector<FullMessageId>> result;
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return result;
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
if (from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid() && from_message_id != MessageId()) {
promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return result;
}
from_message_id = from_message_id.get_next_server_message_id();
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_call_messages_.count(random_id) > 0);
found_call_messages_[random_id]; // reserve place for result
auto filter = only_missed ? MessageSearchFilter::MissedCall : MessageSearchFilter::Call;
if (use_db && G()->parameters().use_message_db) {
// try to use database
MessageId first_db_message_id =
calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)];
int32 message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
auto fixed_from_message_id = from_message_id;
if (fixed_from_message_id == MessageId()) {
fixed_from_message_id = MessageId::max();
}
CHECK(fixed_from_message_id.is_valid() && fixed_from_message_id.is_server());
LOG(INFO) << "Search call messages from " << fixed_from_message_id << ", have up to " << first_db_message_id
<< ", message_count = " << message_count;
if (first_db_message_id < fixed_from_message_id && message_count != -1) {
LOG(INFO) << "Search messages in database from " << fixed_from_message_id << " and with limit " << limit;
MessageDbCallsQuery db_query;
db_query.filter = filter;
db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get();
db_query.limit = limit;
G()->td_db()->get_message_db_async()->get_calls(
db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter, promise = std::move(promise)](
Result<MessageDbCallsResult> calls_result) mutable {
send_closure(G()->messages_manager(), &MessagesManager::on_message_db_calls_result, std::move(calls_result),
random_id, first_db_message_id, filter, std::move(promise));
}));
return result;
}
}
td_->create_handler<SearchMessagesQuery>(std::move(promise))
->send(DialogId(), "", DialogId(), from_message_id, 0, limit, filter, MessageId(), random_id);
return result;
}
void MessagesManager::search_outgoing_document_messages(const string &query, int32 limit,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
td_->create_handler<SearchSentMediaQuery>(std::move(promise))->send(query, limit);
}
void MessagesManager::search_dialog_recent_location_messages(DialogId dialog_id, int32 limit,
Promise<td_api::object_ptr<td_api::messages>> &&promise) {
LOG(INFO) << "Search recent location messages in " << dialog_id << " with limit " << limit;
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
const Dialog *d = get_dialog_force(dialog_id, "search_dialog_recent_location_messages");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
return td_->create_handler<GetRecentLocationsQuery>(std::move(promise))->send(dialog_id, limit);
case DialogType::SecretChat:
return promise.set_value(get_messages_object(0, vector<td_api::object_ptr<td_api::message>>(), true));
default:
UNREACHABLE();
promise.set_error(Status::Error(500, "Search messages is not supported"));
}
}
vector<FullMessageId> MessagesManager::get_active_live_location_messages(Promise<Unit> &&promise) {
if (!G()->parameters().use_message_db) {
are_active_live_location_messages_loaded_ = true;
}
if (!are_active_live_location_messages_loaded_) {
load_active_live_location_messages_queries_.push_back(std::move(promise));
if (load_active_live_location_messages_queries_.size() == 1u) {
LOG(INFO) << "Trying to load active live location messages from database";
G()->td_db()->get_sqlite_pmc()->get(
"di_active_live_location_messages", PromiseCreator::lambda([](string value) {
send_closure(G()->messages_manager(),
&MessagesManager::on_load_active_live_location_full_message_ids_from_database,
std::move(value));
}));
}
return {};
}
promise.set_value(Unit());
vector<FullMessageId> result;
for (auto &full_message_id : active_live_location_full_message_ids_) {
auto m = get_message(full_message_id);
CHECK(m != nullptr);
CHECK(m->content->get_type() == MessageContentType::LiveLocation);
CHECK(!m->message_id.is_scheduled());
if (m->is_failed_to_send) {
continue;
}
auto live_period = get_message_content_live_location_period(m->content.get());
if (live_period <= G()->unix_time() - m->date) { // bool is_expired flag?
// live location is expired
continue;
}
result.push_back(full_message_id);
}
return result;
}
void MessagesManager::on_load_active_live_location_full_message_ids_from_database(string value) {
if (G()->close_flag()) {
return;
}
if (value.empty()) {
LOG(INFO) << "Active live location messages aren't found in the database";
on_load_active_live_location_messages_finished();
if (!active_live_location_full_message_ids_.empty()) {
save_active_live_locations();
}
return;
}
LOG(INFO) << "Successfully loaded active live location messages list of size " << value.size() << " from database";
auto new_full_message_ids = std::move(active_live_location_full_message_ids_);
vector<FullMessageId> old_full_message_ids;
log_event_parse(old_full_message_ids, value).ensure();
// TODO asynchronously load messages from database
active_live_location_full_message_ids_.clear();
for (const auto &full_message_id : old_full_message_ids) {
Message *m = get_message_force(full_message_id, "on_load_active_live_location_full_message_ids_from_database");
if (m != nullptr) {
try_add_active_live_location(full_message_id.get_dialog_id(), m);
}
}
for (const auto &full_message_id : new_full_message_ids) {
add_active_live_location(full_message_id);
}
on_load_active_live_location_messages_finished();
if (!new_full_message_ids.empty() || old_full_message_ids.size() != active_live_location_full_message_ids_.size()) {
save_active_live_locations();
}
}
void MessagesManager::on_load_active_live_location_messages_finished() {
are_active_live_location_messages_loaded_ = true;
set_promises(load_active_live_location_messages_queries_);
}
void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot()) {
return;
}
if (m->content->get_type() != MessageContentType::LiveLocation || m->message_id.is_scheduled() ||
m->message_id.is_local() || m->via_bot_user_id.is_valid() || m->forward_info != nullptr) {
return;
}
auto live_period = get_message_content_live_location_period(m->content.get());
if (live_period <= G()->unix_time() - m->date + 1) { // bool is_expired flag?
// live location is expired
return;
}
add_active_live_location({dialog_id, m->message_id});
}
void MessagesManager::add_active_live_location(FullMessageId full_message_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (!active_live_location_full_message_ids_.insert(full_message_id).second) {
return;
}
// TODO add timer for live location expiration
if (!G()->parameters().use_message_db) {
return;
}
if (are_active_live_location_messages_loaded_) {
save_active_live_locations();
} else if (load_active_live_location_messages_queries_.empty()) {
// load active live locations and save after that
get_active_live_location_messages(Auto());
}
}
bool MessagesManager::delete_active_live_location(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
return active_live_location_full_message_ids_.erase(FullMessageId{dialog_id, m->message_id}) != 0;
}
void MessagesManager::save_active_live_locations() {
CHECK(are_active_live_location_messages_loaded_);
LOG(INFO) << "Save active live locations of size " << active_live_location_full_message_ids_.size() << " to database";
if (G()->parameters().use_message_db) {
G()->td_db()->get_sqlite_pmc()->set("di_active_live_location_messages",
log_event_store(active_live_location_full_message_ids_).as_slice().str(),
Auto());
}
}
void MessagesManager::on_message_live_location_viewed(Dialog *d, const Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->content->get_type() == MessageContentType::LiveLocation);
CHECK(!m->message_id.is_scheduled());
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
switch (d->dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
// ok
break;
case DialogType::SecretChat:
return;
default:
UNREACHABLE();
return;
}
if (!d->is_opened) {
return;
}
if (m->is_outgoing || !m->message_id.is_server() || m->via_bot_user_id.is_valid() || !m->sender_user_id.is_valid() ||
td_->contacts_manager_->is_user_bot(m->sender_user_id) || m->forward_info != nullptr) {
return;
}
auto live_period = get_message_content_live_location_period(m->content.get());
if (live_period <= G()->unix_time() - m->date + 1) {
// live location is expired
return;
}
auto &live_location_task_id = d->pending_viewed_live_locations[m->message_id];
if (live_location_task_id != 0) {
return;
}
live_location_task_id = ++viewed_live_location_task_id_;
auto &full_message_id = viewed_live_location_tasks_[live_location_task_id];
full_message_id = FullMessageId(d->dialog_id, m->message_id);
view_message_live_location_on_server_impl(live_location_task_id, full_message_id);
}
void MessagesManager::view_message_live_location_on_server(int64 task_id) {
if (G()->close_flag()) {
return;
}
auto it = viewed_live_location_tasks_.find(task_id);
if (it == viewed_live_location_tasks_.end()) {
return;
}
auto full_message_id = it->second;
Dialog *d = get_dialog(full_message_id.get_dialog_id());
const Message *m = get_message_force(d, full_message_id.get_message_id(), "view_message_live_location_on_server");
if (m == nullptr || get_message_content_live_location_period(m->content.get()) <= G()->unix_time() - m->date + 1) {
// the message was deleted or live location is expired
viewed_live_location_tasks_.erase(it);
auto erased_count = d->pending_viewed_live_locations.erase(full_message_id.get_message_id());
CHECK(erased_count > 0);
return;
}
view_message_live_location_on_server_impl(task_id, full_message_id);
}
void MessagesManager::view_message_live_location_on_server_impl(int64 task_id, FullMessageId full_message_id) {
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Unit result) {
send_closure(actor_id, &MessagesManager::on_message_live_location_viewed_on_server, task_id);
});
read_message_contents_on_server(full_message_id.get_dialog_id(), {full_message_id.get_message_id()}, 0,
std::move(promise), true);
}
void MessagesManager::on_message_live_location_viewed_on_server(int64 task_id) {
if (G()->close_flag()) {
return;
}
auto it = viewed_live_location_tasks_.find(task_id);
if (it == viewed_live_location_tasks_.end()) {
return;
}
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;
}
if (it->second.message_ids.erase(message_id) && it->second.message_ids.empty()) {
dialog_bot_command_message_ids_.erase(it);
}
}
FileSourceId MessagesManager::get_message_file_source_id(FullMessageId full_message_id, bool force) {
if (!force) {
if (td_->auth_manager_->is_bot()) {
return FileSourceId();
}
auto dialog_id = full_message_id.get_dialog_id();
auto message_id = full_message_id.get_message_id();
if (!dialog_id.is_valid() || !(message_id.is_valid() || message_id.is_valid_scheduled()) ||
dialog_id.get_type() == DialogType::SecretChat || !message_id.is_any_server()) {
return FileSourceId();
}
}
auto &file_source_id = full_message_id_to_file_source_id_[full_message_id];
if (!file_source_id.is_valid()) {
file_source_id = td_->file_reference_manager_->create_message_file_source(full_message_id);
}
return file_source_id;
}
void MessagesManager::add_message_file_sources(DialogId dialog_id, const Message *m) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) {
// return;
}
auto file_ids = get_message_content_file_ids(m->content.get(), td_);
if (file_ids.empty()) {
return;
}
// do not create file_source_id for messages without file_ids
auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, m->message_id));
if (file_source_id.is_valid()) {
for (auto file_id : file_ids) {
td_->file_manager_->add_file_source(file_id, file_source_id);
}
}
}
void MessagesManager::remove_message_file_sources(DialogId dialog_id, const Message *m) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto file_ids = get_message_content_file_ids(m->content.get(), td_);
if (file_ids.empty()) {
return;
}
// do not create file_source_id for messages without file_ids
auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, m->message_id));
if (file_source_id.is_valid()) {
for (auto file_id : file_ids) {
auto file_view = td_->file_manager_->get_file_view(file_id);
send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(),
file_source_id, false, Promise<Unit>());
td_->file_manager_->remove_file_source(file_id, file_source_id);
}
}
}
void MessagesManager::change_message_files(DialogId dialog_id, const Message *m, const vector<FileId> &old_file_ids) {
if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) {
// return;
}
auto new_file_ids = get_message_content_file_ids(m->content.get(), td_);
if (new_file_ids == old_file_ids) {
return;
}
FullMessageId full_message_id{dialog_id, m->message_id};
bool need_delete_files = need_delete_message_files(dialog_id, m);
auto file_source_id = get_message_file_source_id(full_message_id);
for (auto file_id : old_file_ids) {
if (!td::contains(new_file_ids, file_id)) {
if (need_delete_files && need_delete_file(full_message_id, file_id)) {
send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise<Unit>(), "change_message_files");
}
if (file_source_id.is_valid()) {
auto file_view = td_->file_manager_->get_file_view(file_id);
send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(),
file_source_id, false, Promise<Unit>());
}
}
}
if (file_source_id.is_valid()) {
td_->file_manager_->change_files_source(file_source_id, old_file_ids, new_file_ids);
}
}
MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter) {
CHECK(d != nullptr);
auto message_id = filter == MessageSearchFilter::Empty
? d->first_database_message_id
: d->first_database_message_id_by_index[message_search_filter_index(filter)];
CHECK(!message_id.is_scheduled());
if (!message_id.is_valid()) {
if (d->dialog_id.get_type() == DialogType::SecretChat) {
LOG(ERROR) << "Invalid first_database_message_id_by_index in " << d->dialog_id;
return MessageId::min();
}
return MessageId::max();
}
return message_id;
}
void MessagesManager::on_search_dialog_message_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id,
MessageId first_db_message_id, MessageSearchFilter filter,
int32 offset, int32 limit,
Result<vector<MessageDbDialogMessage>> r_messages,
Promise<Unit> promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (r_messages.is_error()) {
LOG(ERROR) << "Failed to get messages from the database: " << r_messages.error();
if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat &&
filter != MessageSearchFilter::FailedToSend) {
found_dialog_messages_.erase(random_id);
}
return promise.set_value(Unit());
}
CHECK(!from_message_id.is_scheduled());
CHECK(!first_db_message_id.is_scheduled());
auto messages = r_messages.move_as_ok();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto it = found_dialog_messages_.find(random_id);
CHECK(it != found_dialog_messages_.end());
auto &res = it->second.second;
res.reserve(messages.size());
for (auto &message : messages) {
auto m = on_get_message_from_database(d, message, false, "on_search_dialog_message_db_result");
if (m != nullptr && first_db_message_id <= m->message_id) {
if (filter == MessageSearchFilter::UnreadMention && !m->contains_unread_mention) {
// skip already read by d->last_read_all_mentions_message_id mentions
} else {
CHECK(!m->message_id.is_scheduled());
res.push_back(m->message_id);
}
}
}
auto &message_count = d->message_count_by_index[message_search_filter_index(filter)];
auto result_size = narrow_cast<int32>(res.size());
bool from_the_end =
from_message_id == MessageId::max() || (offset < 0 && (result_size == 0 || res[0] < from_message_id));
if ((message_count != -1 && message_count < result_size) ||
(message_count > result_size && from_the_end && first_db_message_id == MessageId::min() &&
result_size < limit + offset)) {
LOG(INFO) << "Fix found message count in " << dialog_id << " from " << message_count << " to " << result_size;
message_count = result_size;
if (filter == MessageSearchFilter::UnreadMention) {
d->unread_mention_count = message_count;
update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
if (filter == MessageSearchFilter::UnreadReaction) {
d->unread_reaction_count = message_count;
// update_dialog_mention_notification_count(d);
send_update_chat_unread_reaction_count(d, "on_search_dialog_message_db_result");
}
on_dialog_updated(dialog_id, "on_search_dialog_message_db_result");
}
it->second.first = message_count;
if (res.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) {
LOG(INFO) << "No messages found in database";
found_dialog_messages_.erase(it);
} else {
LOG(INFO) << "Found " << res.size() << " messages out of " << message_count << " in database";
if (from_the_end && filter == MessageSearchFilter::Pinned) {
set_dialog_last_pinned_message_id(d, res.empty() ? MessageId() : res[0]);
}
}
promise.set_value(Unit());
}
td_api::object_ptr<td_api::foundMessages> MessagesManager::get_found_messages_object(
const FoundMessages &found_messages, const char *source) {
vector<tl_object_ptr<td_api::message>> result;
result.reserve(found_messages.full_message_ids.size());
for (const auto &full_message_id : found_messages.full_message_ids) {
auto message = get_message_object(full_message_id, source);
if (message != nullptr) {
result.push_back(std::move(message));
}
}
return td_api::make_object<td_api::foundMessages>(found_messages.total_count, std::move(result),
found_messages.next_offset);
}
MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId dialog_id, const string &query,
string offset, int32 limit,
MessageSearchFilter filter, int64 &random_id,
Promise<Unit> &&promise) {
if (!G()->parameters().use_message_db) {
promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats"));
return {};
}
if (random_id != 0) {
// request has already been sent before
auto it = found_fts_messages_.find(random_id);
CHECK(it != found_fts_messages_.end());
auto result = std::move(it->second);
found_fts_messages_.erase(it);
promise.set_value(Unit());
return result;
}
if (query.empty()) {
promise.set_value(Unit());
return {};
}
if (dialog_id != DialogId() && !have_dialog_force(dialog_id, "offline_search_messages")) {
promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (limit <= 0) {
promise.set_error(Status::Error(400, "Limit must be positive"));
return {};
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
MessageDbFtsQuery fts_query;
fts_query.query = query;
fts_query.dialog_id = dialog_id;
fts_query.filter = filter;
if (!offset.empty()) {
auto r_from_search_id = to_integer_safe<int64>(offset);
if (r_from_search_id.is_error()) {
promise.set_error(Status::Error(400, "Invalid offset specified"));
return {};
}
fts_query.from_search_id = r_from_search_id.ok();
}
fts_query.limit = limit;
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_fts_messages_.count(random_id) > 0);
found_fts_messages_[random_id]; // reserve place for result
G()->td_db()->get_message_db_async()->get_messages_fts(
std::move(fts_query),
PromiseCreator::lambda([random_id, offset = std::move(offset), limit,
promise = std::move(promise)](Result<MessageDbFtsResult> fts_result) mutable {
send_closure(G()->messages_manager(), &MessagesManager::on_message_db_fts_result, std::move(fts_result),
std::move(offset), limit, random_id, std::move(promise));
}));
return {};
}
void MessagesManager::on_message_db_fts_result(Result<MessageDbFtsResult> result, string offset, int32 limit,
int64 random_id, Promise<Unit> &&promise) {
if (G()->close_flag()) {
result = Global::request_aborted_error();
}
if (result.is_error()) {
found_fts_messages_.erase(random_id);
return promise.set_error(result.move_as_error());
}
auto fts_result = result.move_as_ok();
auto it = found_fts_messages_.find(random_id);
CHECK(it != found_fts_messages_.end());
auto &res = it->second.full_message_ids;
res.reserve(fts_result.messages.size());
for (auto &message : fts_result.messages) {
auto m = on_get_message_from_database(message, false, "on_message_db_fts_result");
if (m != nullptr) {
res.emplace_back(message.dialog_id, m->message_id);
}
}
it->second.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id);
it->second.total_count = offset.empty() && fts_result.messages.size() < static_cast<size_t>(limit)
? static_cast<int32>(fts_result.messages.size())
: -1;
promise.set_value(Unit());
}
void MessagesManager::on_message_db_calls_result(Result<MessageDbCallsResult> result, int64 random_id,
MessageId first_db_message_id, MessageSearchFilter filter,
Promise<Unit> &&promise) {
if (G()->close_flag()) {
result = Global::request_aborted_error();
}
if (result.is_error()) {
found_call_messages_.erase(random_id);
return promise.set_error(result.move_as_error());
}
auto calls_result = result.move_as_ok();
auto it = found_call_messages_.find(random_id);
CHECK(it != found_call_messages_.end());
auto &res = it->second.second;
CHECK(!first_db_message_id.is_scheduled());
res.reserve(calls_result.messages.size());
for (auto &message : calls_result.messages) {
auto m = on_get_message_from_database(message, false, "on_message_db_calls_result");
if (m != nullptr && first_db_message_id <= m->message_id) {
res.emplace_back(message.dialog_id, m->message_id);
}
}
it->second.first = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
if (res.empty() && first_db_message_id != MessageId::min()) {
LOG(INFO) << "No messages found in database";
found_call_messages_.erase(it);
}
promise.set_value(Unit());
}
MessagesManager::FoundMessages MessagesManager::search_messages(FolderId folder_id, bool ignore_folder_id,
const string &query, const string &offset, int32 limit,
MessageSearchFilter filter, int32 min_date,
int32 max_date, int64 &random_id,
Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_messages_.find(random_id);
CHECK(it != found_messages_.end());
auto result = std::move(it->second);
found_messages_.erase(it);
promise.set_value(Unit());
return result;
}
if (limit <= 0) {
promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
int32 offset_date = std::numeric_limits<int32>::max();
DialogId offset_dialog_id;
MessageId offset_message_id;
bool is_offset_valid = [&] {
if (offset.empty()) {
return true;
}
auto parts = full_split(offset, ',');
if (parts.size() != 3) {
return false;
}
auto r_offset_date = to_integer_safe<int32>(parts[0]);
auto r_offset_dialog_id = to_integer_safe<int64>(parts[1]);
auto r_offset_message_id = to_integer_safe<int32>(parts[2]);
if (r_offset_date.is_error() || r_offset_date.ok() <= 0 || r_offset_message_id.is_error() ||
r_offset_dialog_id.is_error()) {
return false;
}
offset_date = r_offset_date.ok();
offset_message_id = MessageId(ServerMessageId(r_offset_message_id.ok()));
offset_dialog_id = DialogId(r_offset_dialog_id.ok());
if (!offset_message_id.is_valid() || !offset_dialog_id.is_valid() ||
MessagesManager::get_input_peer_force(offset_dialog_id)->get_id() == telegram_api::inputPeerEmpty::ID) {
return false;
}
return true;
}();
if (!is_offset_valid) {
promise.set_error(Status::Error(400, "Invalid offset specified"));
return {};
}
CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
if (filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention ||
filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::FailedToSend ||
filter == MessageSearchFilter::Pinned) {
promise.set_error(Status::Error(400, "The filter is not supported"));
return {};
}
if (query.empty() && filter == MessageSearchFilter::Empty) {
promise.set_value(Unit());
return {};
}
do {
random_id = Random::secure_int64();
} while (random_id == 0 || found_messages_.count(random_id) > 0);
found_messages_[random_id]; // reserve place for result
LOG(DEBUG) << "Search all messages filtered by " << filter << " with query = \"" << query << "\" from offset "
<< offset << " and limit " << limit;
td_->create_handler<SearchMessagesGlobalQuery>(std::move(promise))
->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, filter,
min_date, max_date, random_id);
return {};
}
int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_by_date");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return 0;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return 0;
}
if (date <= 0) {
date = 1;
}
int64 random_id = 0;
do {
random_id = Random::secure_int64();
} while (random_id == 0 || get_dialog_message_by_date_results_.count(random_id) > 0);
get_dialog_message_by_date_results_[random_id]; // reserve place for result
auto message_id = find_message_by_date(d->messages.get(), date);
if (message_id.is_valid() && (message_id == d->last_message_id || get_message(d, message_id)->have_next)) {
get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id};
promise.set_value(Unit());
return random_id;
}
if (G()->parameters().use_message_db && d->last_database_message_id != MessageId()) {
CHECK(d->first_database_message_id != MessageId());
G()->td_db()->get_message_db_async()->get_dialog_message_by_date(
dialog_id, d->first_database_message_id, d->last_database_message_id, date,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, date, random_id,
promise = std::move(promise)](Result<MessageDbDialogMessage> result) mutable {
send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_from_database, dialog_id, date,
random_id, std::move(result), std::move(promise));
}));
} else {
get_dialog_message_by_date_from_server(d, date, random_id, false, std::move(promise));
}
return random_id;
}
void MessagesManager::run_affected_history_query_until_complete(DialogId dialog_id, AffectedHistoryQuery query,
bool get_affected_messages, Promise<Unit> &&promise) {
CHECK(!G()->close_flag());
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, query, get_affected_messages,
promise = std::move(promise)](Result<AffectedHistory> &&result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
send_closure(actor_id, &MessagesManager::on_get_affected_history, dialog_id, query, get_affected_messages,
result.move_as_ok(), std::move(promise));
});
query(dialog_id, std::move(query_promise));
}
void MessagesManager::on_get_affected_history(DialogId dialog_id, AffectedHistoryQuery query,
bool get_affected_messages, AffectedHistory affected_history,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
LOG(INFO) << "Receive " << (affected_history.is_final_ ? "final " : "partial ")
<< "affected history with pts = " << affected_history.pts_
<< " and pts_count = " << affected_history.pts_count_;
if (affected_history.pts_count_ > 0) {
if (get_affected_messages) {
affected_history.pts_count_ = 0;
}
auto update_promise = affected_history.is_final_ ? std::move(promise) : Promise<Unit>();
if (dialog_id.get_type() == DialogType::Channel) {
add_pending_channel_update(dialog_id, make_tl_object<dummyUpdate>(), affected_history.pts_,
affected_history.pts_count_, std::move(update_promise), "on_get_affected_history");
} else {
td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_history.pts_,
affected_history.pts_count_, Time::now(), std::move(update_promise),
"on_get_affected_history");
}
} else if (affected_history.is_final_) {
promise.set_value(Unit());
}
if (!affected_history.is_final_) {
run_affected_history_query_until_complete(dialog_id, std::move(query), get_affected_messages, std::move(promise));
}
}
MessageId MessagesManager::find_message_by_date(const Message *m, int32 date) {
if (m == nullptr) {
return MessageId();
}
if (m->date > date) {
return find_message_by_date(m->left.get(), date);
}
auto message_id = find_message_by_date(m->right.get(), date);
if (message_id.is_valid()) {
return message_id;
}
return m->message_id;
}
void MessagesManager::find_messages_by_date(const Message *m, int32 min_date, int32 max_date,
vector<MessageId> &message_ids) {
if (m == nullptr) {
return;
}
if (m->date >= min_date) {
find_messages_by_date(m->left.get(), min_date, max_date, message_ids);
if (m->date <= max_date) {
message_ids.push_back(m->message_id);
}
}
if (m->date <= max_date) {
find_messages_by_date(m->right.get(), min_date, max_date, message_ids);
}
}
void MessagesManager::on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id,
Result<MessageDbDialogMessage> result,
Promise<Unit> promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (result.is_ok()) {
Message *m = on_get_message_from_database(d, result.ok(), false, "on_get_dialog_message_by_date_from_database");
if (m != nullptr) {
auto message_id = find_message_by_date(d->messages.get(), date);
if (!message_id.is_valid()) {
LOG(ERROR) << "Failed to find " << m->message_id << " in " << dialog_id << " by date " << date;
message_id = m->message_id;
}
get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id};
promise.set_value(Unit());
return;
}
// TODO if m == nullptr, we need to just adjust it to the next non-nullptr message, not get from server
}
return get_dialog_message_by_date_from_server(d, date, random_id, true, std::move(promise));
}
void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id,
bool after_database_search, Promise<Unit> &&promise) {
CHECK(d != nullptr);
if (d->have_full_history) {
// request can always be done locally/in memory. There is no need to send request to the server
if (after_database_search) {
return promise.set_value(Unit());
}
auto message_id = find_message_by_date(d->messages.get(), date);
if (message_id.is_valid()) {
get_dialog_message_by_date_results_[random_id] = {d->dialog_id, message_id};
}
promise.set_value(Unit());
return;
}
if (d->dialog_id.get_type() == DialogType::SecretChat) {
// there is no way to send request to the server
return promise.set_value(Unit());
}
td_->create_handler<GetDialogMessageByDateQuery>(std::move(promise))->send(d->dialog_id, date, random_id);
}
void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id,
vector<tl_object_ptr<telegram_api::Message>> &&messages,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
auto it = get_dialog_message_by_date_results_.find(random_id);
CHECK(it != get_dialog_message_by_date_results_.end());
auto &result = it->second;
CHECK(result == FullMessageId());
for (auto &message : messages) {
auto message_date = get_message_date(message);
auto message_dialog_id = DialogId::get_message_dialog_id(message);
if (message_dialog_id != dialog_id) {
LOG(ERROR) << "Receive message in wrong " << message_dialog_id << " instead of " << dialog_id;
continue;
}
if (message_date != 0 && message_date <= date) {
result = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, false,
false, "on_get_dialog_message_by_date_success");
if (result != FullMessageId()) {
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto message_id = find_message_by_date(d->messages.get(), date);
if (!message_id.is_valid()) {
LOG(ERROR) << "Failed to find " << result.get_message_id() << " in " << dialog_id << " by date " << date;
message_id = result.get_message_id();
}
get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id};
// TODO result must be adjusted by local messages
promise.set_value(Unit());
return;
}
}
}
promise.set_value(Unit());
}
void MessagesManager::on_get_dialog_message_by_date_fail(int64 random_id) {
auto erased_count = get_dialog_message_by_date_results_.erase(random_id);
CHECK(erased_count > 0);
}
tl_object_ptr<td_api::message> MessagesManager::get_dialog_message_by_date_object(int64 random_id) {
auto it = get_dialog_message_by_date_results_.find(random_id);
CHECK(it != get_dialog_message_by_date_results_.end());
auto full_message_id = std::move(it->second);
get_dialog_message_by_date_results_.erase(it);
return get_message_object(full_message_id, "get_dialog_message_by_date_object");
}
void MessagesManager::get_dialog_sparse_message_positions(
DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit,
Promise<td_api::object_ptr<td_api::messagePositions>> &&promise) {
const Dialog *d = get_dialog_force(dialog_id, "get_dialog_sparse_message_positions");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (limit < 50 || limit > 2000) { // server-side limits
return promise.set_error(Status::Error(400, "Invalid limit specified"));
}
CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention ||
filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction ||
filter == MessageSearchFilter::Pinned) {
return promise.set_error(Status::Error(400, "The filter is not supported"));
}
if (from_message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Invalid from_message_id specified"));
}
if (!from_message_id.is_valid() || from_message_id > d->last_new_message_id) {
if (d->last_new_message_id.is_valid()) {
from_message_id = d->last_new_message_id.get_next_message_id(MessageType::Server);
} else {
from_message_id = MessageId::max();
}
} else {
from_message_id = from_message_id.get_next_server_message_id();
}
if (filter == MessageSearchFilter::FailedToSend || dialog_id.get_type() == DialogType::SecretChat) {
if (!G()->parameters().use_message_db) {
return promise.set_error(Status::Error(400, "Unsupported without message database"));
}
LOG(INFO) << "Get sparse message positions from database";
auto new_promise =
PromiseCreator::lambda([promise = std::move(promise)](Result<MessageDbMessagePositions> result) mutable {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
auto positions = result.move_as_ok();
promise.set_value(td_api::make_object<td_api::messagePositions>(
positions.total_count, transform(positions.positions, [](const MessageDbMessagePosition &position) {
return td_api::make_object<td_api::messagePosition>(position.position, position.message_id.get(),
position.date);
})));
});
MessageDbGetDialogSparseMessagePositionsQuery db_query;
db_query.dialog_id = dialog_id;
db_query.filter = filter;
db_query.from_message_id = from_message_id;
db_query.limit = limit;
G()->td_db()->get_message_db_async()->get_dialog_sparse_message_positions(db_query, std::move(new_promise));
return;
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
td_->create_handler<GetSearchResultPositionsQuery>(std::move(promise))
->send(dialog_id, filter, from_message_id, limit);
break;
case DialogType::SecretChat:
case DialogType::None:
default:
UNREACHABLE();
}
}
void MessagesManager::on_get_dialog_sparse_message_positions(
DialogId dialog_id, MessageSearchFilter filter,
telegram_api::object_ptr<telegram_api::messages_searchResultsPositions> positions,
Promise<td_api::object_ptr<td_api::messagePositions>> &&promise) {
auto message_positions = transform(
positions->positions_, [](const telegram_api::object_ptr<telegram_api::searchResultPosition> &position) {
return td_api::make_object<td_api::messagePosition>(
position->offset_, MessageId(ServerMessageId(position->msg_id_)).get(), position->date_);
});
promise.set_value(td_api::make_object<td_api::messagePositions>(positions->count_, std::move(message_positions)));
}
void MessagesManager::get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local,
Promise<int32> &&promise) {
LOG(INFO) << "Get " << (return_local ? "local " : "") << "number of messages in " << dialog_id << " filtered by "
<< filter;
const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_count");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (filter == MessageSearchFilter::Empty) {
return promise.set_error(Status::Error(400, "Can't use searchMessagesFilterEmpty"));
}
auto dialog_type = dialog_id.get_type();
int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
if (message_count == -1 && filter == MessageSearchFilter::UnreadMention) {
message_count = d->unread_mention_count;
}
if (message_count == -1 && filter == MessageSearchFilter::UnreadReaction) {
message_count = d->unread_reaction_count;
}
if (message_count != -1 || return_local || dialog_type == DialogType::SecretChat ||
filter == MessageSearchFilter::FailedToSend) {
return promise.set_value(std::move(message_count));
}
get_dialog_message_count_from_server(dialog_id, filter, std::move(promise));
}
void MessagesManager::get_dialog_message_count_from_server(DialogId dialog_id, MessageSearchFilter filter,
Promise<int32> &&promise) {
LOG(INFO) << "Get number of messages in " << dialog_id << " filtered by " << filter << " from the server";
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
td_->create_handler<GetSearchCountersQuery>(std::move(promise))->send(dialog_id, filter);
break;
case DialogType::None:
case DialogType::SecretChat:
default:
UNREACHABLE();
}
}
void MessagesManager::get_dialog_message_position(FullMessageId full_message_id, MessageSearchFilter filter,
MessageId top_thread_message_id, Promise<int32> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_position");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
auto message_id = full_message_id.get_message_id();
const Message *m = get_message_force(d, message_id, "get_dialog_message_position");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!m->message_id.is_valid() || !m->message_id.is_server() ||
(filter != MessageSearchFilter::Empty &&
(get_message_index_mask(d->dialog_id, m) & message_search_filter_index_mask(filter)) == 0)) {
return promise.set_error(Status::Error(400, "Message can't be found in the filter"));
}
if (top_thread_message_id != MessageId()) {
if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
return promise.set_error(Status::Error(400, "Invalid message thread identifier specified"));
}
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
return promise.set_error(Status::Error(400, "Can't filter by message thread identifier in the chat"));
}
if (m->top_thread_message_id != top_thread_message_id ||
(m->message_id == top_thread_message_id && !m->is_topic_message)) {
return promise.set_error(Status::Error(400, "Message doesn't belong to the message thread"));
}
}
if (dialog_id.get_type() == DialogType::SecretChat) {
return promise.set_error(Status::Error(400, "The method can't be used in secret chats"));
}
if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction ||
filter == MessageSearchFilter::FailedToSend) {
return promise.set_error(Status::Error(400, "The filter is not supported"));
}
td_->create_handler<GetMessagePositionQuery>(std::move(promise))
->send(dialog_id, message_id, filter, top_thread_message_id);
}
void MessagesManager::preload_newer_messages(const Dialog *d, MessageId max_message_id) {
CHECK(d != nullptr);
CHECK(max_message_id.is_valid());
if (td_->auth_manager_->is_bot()) {
return;
}
MessagesConstIterator p(d, max_message_id);
int32 limit = MAX_GET_HISTORY * 3 / 10;
while (*p != nullptr && limit-- > 0) {
++p;
if (*p) {
max_message_id = (*p)->message_id;
}
}
if (limit > 0 && (d->last_message_id == MessageId() || max_message_id < d->last_message_id)) {
// need to preload some new messages
LOG(INFO) << "Preloading newer after " << max_message_id;
load_messages_impl(d, max_message_id, -MAX_GET_HISTORY + 1, MAX_GET_HISTORY, 3, false, Promise<Unit>());
}
}
void MessagesManager::preload_older_messages(const Dialog *d, MessageId min_message_id) {
CHECK(d != nullptr);
CHECK(min_message_id.is_valid());
if (td_->auth_manager_->is_bot()) {
return;
}
/*
if (d->first_remote_message_id == -1) {
// nothing left to preload from server
return;
}
*/
MessagesConstIterator p(d, min_message_id);
int32 limit = MAX_GET_HISTORY * 3 / 10 + 1;
while (*p != nullptr && limit-- > 0) {
min_message_id = (*p)->message_id;
--p;
}
if (limit > 0) {
// need to preload some old messages
LOG(INFO) << "Preloading older before " << min_message_id;
load_messages_impl(d, min_message_id, 0, MAX_GET_HISTORY / 2, 3, false, Promise<Unit>());
}
}
unique_ptr<MessagesManager::Message> MessagesManager::parse_message(Dialog *d, MessageId expected_message_id,
const BufferSlice &value, bool is_scheduled) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
auto m = make_unique<Message>();
auto status = log_event_parse(*m, value.as_slice());
bool is_message_id_valid = [&] {
if (is_scheduled) {
if (!expected_message_id.is_valid_scheduled()) {
return false;
}
if (m->message_id == expected_message_id) {
return true;
}
return m->message_id.is_valid_scheduled() && expected_message_id.is_scheduled_server() &&
m->message_id.is_scheduled_server() &&
m->message_id.get_scheduled_server_message_id() == expected_message_id.get_scheduled_server_message_id();
} else {
if (!expected_message_id.is_valid()) {
return false;
}
return m->message_id == expected_message_id;
}
}();
if (status.is_error() || !is_message_id_valid) {
// can't happen unless the database is broken, but has been seen in the wild
LOG(ERROR) << "Receive invalid message from database: " << expected_message_id << ' ' << m->message_id << ' '
<< status << ' ' << format::as_hex_dump<4>(value.as_slice());
if (!is_scheduled && dialog_id.get_type() != DialogType::SecretChat) {
// trying to repair the message
if (expected_message_id.is_valid() && expected_message_id.is_server()) {
get_message_from_server({dialog_id, expected_message_id}, Auto(), "parse_message");
}
if (m->message_id.is_valid() && m->message_id.is_server()) {
get_message_from_server({dialog_id, m->message_id}, Auto(), "parse_message");
}
}
return nullptr;
}
if (m->reactions != nullptr) {
if (m->available_reactions_generation < d->available_reactions_generation) {
m->reactions = nullptr;
m->available_reactions_generation = 0;
} else if (m->available_reactions_generation > d->available_reactions_generation &&
m->available_reactions_generation - d->available_reactions_generation < 1000000000) {
switch (dialog_id.get_type()) {
case DialogType::Chat:
case DialogType::Channel:
LOG(ERROR) << "Fix available_reactions_generation in " << dialog_id << " from "
<< d->available_reactions_generation << " to " << m->available_reactions_generation;
hide_dialog_message_reactions(d);
set_dialog_next_available_reactions_generation(d, m->available_reactions_generation);
on_dialog_updated(dialog_id, "parse_message");
break;
case DialogType::User:
case DialogType::SecretChat:
default:
LOG(ERROR) << "Receive available_reactions_generation = " << m->available_reactions_generation << " in "
<< m->message_id << " in " << dialog_id;
break;
}
}
}
if (m->history_generation > d->history_generation && m->history_generation - d->history_generation < 1000000000) {
switch (dialog_id.get_type()) {
case DialogType::Channel:
LOG(ERROR) << "Fix history_generation in " << dialog_id << " from " << d->history_generation << " to "
<< m->history_generation;
d->history_generation = m->history_generation + 1;
on_dialog_updated(dialog_id, "parse_message");
break;
case DialogType::Chat:
case DialogType::User:
case DialogType::SecretChat:
default:
LOG(ERROR) << "Receive history_generation = " << m->history_generation << " in " << m->message_id << " in "
<< dialog_id;
break;
}
}
LOG(INFO) << "Loaded " << m->message_id << " in " << dialog_id << " of size " << value.size() << " from database";
return m;
}
void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId from_message_id,
MessageId old_last_database_message_id, int32 offset, int32 limit,
bool from_the_end, bool only_local,
vector<MessageDbDialogMessage> &&messages, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(-limit < offset && offset <= 0);
CHECK(offset < 0 || from_the_end);
CHECK(!from_message_id.is_scheduled());
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(WARNING) << "Ignore result of get_history_from_database in " << dialog_id;
promise.set_value(Unit());
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
LOG(INFO) << "Receive " << messages.size() << " history messages from database "
<< (from_the_end ? "from the end " : "") << "in " << dialog_id << " from " << from_message_id
<< " with offset " << offset << " and limit " << limit << ". First database message is "
<< d->first_database_message_id << ", last database message is " << d->last_database_message_id
<< ", have_full_history = " << d->have_full_history
<< ", have_full_history_source = " << d->have_full_history_source;
if (old_last_database_message_id < d->last_database_message_id && old_last_database_message_id < from_message_id) {
// new messages where added to the database since the request was sent
// they should have been received from the database, so we must repeat the request to get them
if (from_the_end) {
get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 20");
} else {
get_history_impl(d, from_message_id, offset, limit, true, only_local, std::move(promise));
}
return;
}
if (messages.empty() && from_the_end && d->messages == nullptr) {
if (d->have_full_history) {
set_dialog_is_empty(d, "on_get_history_from_database empty");
} else if (d->last_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database empty");
set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database empty");
}
}
bool have_next = false;
bool need_update = false;
bool need_update_dialog_pos = false;
bool added_new_message = false;
MessageId first_added_message_id;
MessageId last_added_message_id;
Message *next_message = nullptr;
Dependencies dependencies;
bool is_first = true;
bool had_full_history = d->have_full_history;
auto debug_first_database_message_id = d->first_database_message_id;
auto debug_last_message_id = d->last_message_id;
auto debug_last_new_message_id = d->last_new_message_id;
auto first_received_message_id = MessageId::max();
MessageId last_received_message_id;
size_t pos = 0;
for (auto &message_slice : messages) {
if (!d->first_database_message_id.is_valid() && !d->have_full_history) {
break;
}
auto message = parse_message(d, message_slice.message_id, message_slice.data, false);
if (message == nullptr) {
if (d->have_full_history) {
d->have_full_history = false;
d->have_full_history_source = 0;
d->is_empty = false; // just in case
on_dialog_updated(dialog_id, "drop have_full_history in on_get_history_from_database");
}
break;
}
if (message->message_id >= first_received_message_id) {
LOG(ERROR) << "Receive " << message->message_id << " after " << first_received_message_id
<< " from database in the history of " << dialog_id << " from " << from_message_id << " with offset "
<< offset << ", limit " << limit << ", from_the_end = " << from_the_end;
break;
}
first_received_message_id = message->message_id;
if (!last_received_message_id.is_valid()) {
last_received_message_id = message->message_id;
}
if (message->message_id < d->first_database_message_id) {
if (d->have_full_history) {
LOG(ERROR) << "Have full history in the " << dialog_id << " and receive " << message->message_id
<< " from database, but first database message is " << d->first_database_message_id;
} else {
break;
}
}
if (!have_next && (from_the_end || (is_first && offset < -1 && message->message_id <= from_message_id)) &&
message->message_id < d->last_message_id) {
// last message in the dialog must be attached to the next local message
have_next = true;
}
message->have_previous = false;
message->have_next = have_next;
message->from_database = true;
auto old_message = get_message(d, message->message_id);
Message *m = old_message ? old_message
: add_message_to_dialog(d, std::move(message), false, &need_update,
&need_update_dialog_pos, "on_get_history_from_database");
if (m != nullptr) {
first_added_message_id = m->message_id;
if (!last_added_message_id.is_valid()) {
last_added_message_id = m->message_id;
}
if (old_message == nullptr) {
add_message_dependencies(dependencies, m);
added_new_message = true;
} else if (m->message_id != from_message_id) {
added_new_message = true;
}
if (next_message != nullptr && !next_message->have_previous) {
LOG_CHECK(m->message_id < next_message->message_id)
<< m->message_id << ' ' << next_message->message_id << ' ' << first_received_message_id << ' '
<< last_received_message_id << ' ' << dialog_id << ' ' << from_message_id << ' ' << offset << ' ' << limit
<< ' ' << from_the_end << ' ' << only_local << ' ' << messages.size() << ' '
<< debug_first_database_message_id << ' ' << last_added_message_id << ' ' << added_new_message << ' ' << pos
<< ' ' << m << ' ' << next_message << ' ' << old_message << ' '
<< to_string(get_message_object(dialog_id, m, "on_get_history_from_database"))
<< to_string(get_message_object(dialog_id, next_message, "on_get_history_from_database"));
LOG(INFO) << "Fix have_previous for " << next_message->message_id;
next_message->have_previous = true;
attach_message_to_previous(
d, next_message->message_id,
(PSLICE() << "on_get_history_from_database 1 " << m->message_id << ' ' << from_message_id << ' ' << offset
<< ' ' << limit << ' ' << d->first_database_message_id << ' ' << d->have_full_history << ' '
<< pos)
.c_str());
}
have_next = true;
next_message = m;
}
is_first = false;
pos++;
}
dependencies.resolve_force(td_, "on_get_history_from_database");
if (from_the_end && !last_added_message_id.is_valid() && d->first_database_message_id.is_valid() &&
!d->have_full_history) {
if (first_received_message_id <= d->first_database_message_id) {
// database definitely has no messages from first_database_message_id to last_database_message_id; drop them
set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 8");
set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 9");
} else {
CHECK(first_received_message_id.is_valid());
// if a message was received, but wasn't added, then it is likely to be already deleted
// if it is less than d->last_database_message_id, then we can adjust d->last_database_message_id and
// try again database search without chance to loop
if (first_received_message_id < d->last_database_message_id) {
set_dialog_last_database_message_id(d, first_received_message_id, "on_get_history_from_database 12");
get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 21");
return;
}
if (limit > 1) {
// we expected to have messages [first_database_message_id, last_database_message_id] in the database, but
// received messages [first_received_message_id, last_received_message_id], none of which can be added
// first_database_message_id and last_database_message_id are very wrong, so it is better to drop them,
// pretending that the database has no usable messages
if (!is_deleted_message(d, d->first_database_message_id) ||
!is_deleted_message(d, d->last_database_message_id)) {
if (first_received_message_id == MessageId::max()) {
CHECK(last_received_message_id == MessageId());
LOG(ERROR) << "Receive no usable messages in " << dialog_id
<< " from database from the end, but expected messages from " << d->first_database_message_id
<< " up to " << d->last_database_message_id
<< ". Have old last_database_message_id = " << old_last_database_message_id << " and "
<< messages.size() << " received messages";
} else {
LOG(ERROR) << "Receive " << messages.size() << " unusable messages [" << first_received_message_id
<< " ... " << last_received_message_id << "] in " << dialog_id
<< " from database from the end, but expected messages from " << d->first_database_message_id
<< " up to " << d->last_database_message_id;
}
}
set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 13");
set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 14");
}
}
}
if (!added_new_message && !only_local && dialog_id.get_type() != DialogType::SecretChat) {
if (from_the_end) {
from_message_id = MessageId();
}
load_messages_impl(d, from_message_id, offset, limit, 1, false, std::move(promise));
return;
}
if (from_the_end && last_added_message_id.is_valid()) {
CHECK(next_message != nullptr);
// CHECK(d->first_database_message_id.is_valid());
// CHECK(last_added_message_id >= d->first_database_message_id);
if ((had_full_history || d->have_full_history) && !d->last_new_message_id.is_valid() &&
(last_added_message_id.is_server() || d->dialog_id.get_type() == DialogType::SecretChat)) {
LOG(ERROR) << "Trying to hard fix " << d->dialog_id << " last new message to " << last_added_message_id
<< " from on_get_history_from_database 2";
d->last_new_message_id = last_added_message_id;
on_dialog_updated(d->dialog_id, "on_get_history_from_database 3");
}
if (last_added_message_id > d->last_message_id && d->last_new_message_id.is_valid()) {
set_dialog_last_message_id(d, last_added_message_id, "on_get_history_from_database 4");
need_update_dialog_pos = true;
}
if (last_added_message_id != d->last_database_message_id && d->last_new_message_id.is_valid()) {
auto debug_last_database_message_id = d->last_database_message_id;
auto debug_set_dialog_last_database_message_id = d->debug_set_dialog_last_database_message_id;
if (!d->first_database_message_id.is_valid() && !d->last_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, next_message->message_id, "on_get_history_from_database 5");
}
set_dialog_last_database_message_id(d, last_added_message_id, "on_get_history_from_database 5");
if (last_added_message_id < d->first_database_message_id || !d->first_database_message_id.is_valid()) {
CHECK(next_message != nullptr);
LOG_CHECK(had_full_history || d->have_full_history)
<< had_full_history << ' ' << d->have_full_history << ' ' << next_message->message_id << ' '
<< last_added_message_id << ' ' << d->first_database_message_id << ' ' << debug_first_database_message_id
<< ' ' << d->last_database_message_id << ' ' << debug_last_database_message_id << ' ' << dialog_id << ' '
<< d->last_new_message_id << ' ' << debug_last_new_message_id << ' ' << d->last_message_id << ' '
<< debug_last_message_id << ' ' << debug_set_dialog_last_database_message_id << ' '
<< d->debug_set_dialog_last_database_message_id << ' ' << first_received_message_id << ' '
<< last_received_message_id << ' ' << d->debug_first_database_message_id << ' '
<< d->debug_last_database_message_id << ' ' << d->debug_last_new_message_id << ' '
<< d->have_full_history_source;
CHECK(next_message->message_id <= d->last_database_message_id);
LOG(ERROR) << "Fix first database message in " << dialog_id << " from " << d->first_database_message_id
<< " to " << next_message->message_id;
set_dialog_first_database_message_id(d, next_message->message_id, "on_get_history_from_database 6");
}
}
}
if (first_added_message_id.is_valid() && first_added_message_id != d->first_database_message_id &&
first_received_message_id < d->first_database_message_id && d->last_new_message_id.is_valid() &&
!d->have_full_history) {
CHECK(first_added_message_id > d->first_database_message_id);
set_dialog_first_database_message_id(d, first_added_message_id, "on_get_history_from_database 10");
if (d->last_database_message_id < d->first_database_message_id) {
set_dialog_last_database_message_id(d, d->first_database_message_id, "on_get_history_from_database 11");
}
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "on_get_history_from_database 7");
}
promise.set_value(Unit());
}
void MessagesManager::get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local,
Promise<Unit> &&promise) {
get_history_from_the_end_impl(get_dialog(dialog_id), from_database, only_local, std::move(promise),
"get_history_from_the_end");
}
void MessagesManager::get_history_from_the_end_impl(const Dialog *d, bool from_database, bool only_local,
Promise<Unit> &&promise, const char *source) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// can't get history in dialogs without read access
return promise.set_value(Unit());
}
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) {
// repair last database message ID
limit = 10;
}
LOG(INFO) << "Get history from the end of " << dialog_id << " from database from " << source;
MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.from_message_id = MessageId::max();
db_query.limit = limit;
G()->td_db()->get_message_db_async()->get_messages(
db_query, PromiseCreator::lambda([dialog_id, old_last_database_message_id = d->last_database_message_id,
only_local, limit, actor_id = actor_id(this), promise = std::move(promise)](
vector<MessageDbDialogMessage> messages) mutable {
send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, MessageId::max(),
old_last_database_message_id, 0, limit, true, only_local, std::move(messages),
std::move(promise));
}));
} else {
if (only_local || dialog_id.get_type() == DialogType::SecretChat || d->last_message_id.is_valid()) {
// if last message is known, there are no reasons to get message history from server from the end
promise.set_value(Unit());
return;
}
if (!promise && !G()->parameters().use_message_db) {
// repair last message ID
limit = 10;
}
LOG(INFO) << "Get history from the end of " << dialog_id << " from server from " << source;
td_->create_handler<GetHistoryQuery>(std::move(promise))
->send_get_from_the_end(dialog_id, d->last_new_message_id, limit);
}
}
void MessagesManager::get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
bool from_database, bool only_local, Promise<Unit> &&promise) {
get_history_impl(get_dialog(dialog_id), from_message_id, offset, limit, from_database, only_local,
std::move(promise));
}
void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit,
bool from_database, bool only_local, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(d != nullptr);
CHECK(from_message_id.is_valid());
auto dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// can't get history in dialogs without read access
return promise.set_value(Unit());
}
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";
MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.from_message_id = from_message_id;
db_query.offset = offset;
db_query.limit = limit;
G()->td_db()->get_message_db_async()->get_messages(
db_query,
PromiseCreator::lambda([dialog_id, from_message_id, old_last_database_message_id = d->last_database_message_id,
offset, limit, only_local, actor_id = actor_id(this),
promise = std::move(promise)](vector<MessageDbDialogMessage> messages) mutable {
send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, from_message_id,
old_last_database_message_id, offset, limit, false, only_local, std::move(messages),
std::move(promise));
}));
} else {
if (only_local || dialog_id.get_type() == DialogType::SecretChat) {
return promise.set_value(Unit());
}
LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset
<< " and limit " << limit << " from server";
td_->create_handler<GetHistoryQuery>(std::move(promise))
->send(dialog_id, from_message_id.get_next_server_message_id(), d->last_new_message_id, offset, limit);
}
}
void MessagesManager::load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
int left_tries, bool only_local, Promise<Unit> &&promise) {
load_messages_impl(get_dialog(dialog_id), from_message_id, offset, limit, left_tries, only_local, std::move(promise));
}
void MessagesManager::load_messages_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit,
int left_tries, bool only_local, Promise<Unit> &&promise) {
CHECK(d != nullptr);
CHECK(offset <= 0);
CHECK(left_tries > 0);
auto dialog_id = d->dialog_id;
LOG(INFO) << "Load " << (only_local ? "local " : "") << "messages in " << dialog_id << " from " << from_message_id
<< " with offset = " << offset << " and limit = " << limit << ". " << left_tries << " tries left";
only_local |= dialog_id.get_type() == DialogType::SecretChat;
if (!only_local && d->have_full_history) {
LOG(INFO) << "Have full history in " << dialog_id << ", so don't need to get chat history from server";
only_local = true;
}
bool from_database = (left_tries > 2 || only_local) && G()->parameters().use_message_db;
if (from_message_id == MessageId()) {
get_history_from_the_end_impl(d, from_database, only_local, std::move(promise), "load_messages_impl");
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);
offset = -1;
} else {
// get history around some server or local message
int32 messages_to_load = max(MAX_GET_HISTORY, limit);
int32 max_add = max(messages_to_load - limit - 2, 0);
offset -= max_add;
limit = MAX_GET_HISTORY;
}
get_history_impl(d, from_message_id, offset, limit, from_database, only_local, std::move(promise));
}
vector<MessageId> MessagesManager::get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result,
Promise<Unit> &&promise) {
if (G()->close_flag()) {
promise.set_error(Global::request_aborted_error());
return {};
}
LOG(INFO) << "Get scheduled messages in " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "get_dialog_scheduled_messages");
if (d == nullptr) {
promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return {};
}
if (is_broadcast_channel(dialog_id) &&
!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) {
promise.set_error(Status::Error(400, "Not enough rights to get scheduled messages"));
return {};
}
if (!d->has_loaded_scheduled_messages_from_database) {
load_dialog_scheduled_messages(dialog_id, true, 0, std::move(promise));
return {};
}
vector<MessageId> message_ids;
find_old_messages(d->scheduled_messages.get(),
MessageId(ScheduledServerMessageId(), std::numeric_limits<int32>::max(), true), message_ids);
std::reverse(message_ids.begin(), message_ids.end());
if (G()->parameters().use_message_db) {
bool has_scheduled_database_messages = false;
for (auto &message_id : message_ids) {
CHECK(message_id.is_valid_scheduled());
if (!message_id.is_yet_unsent()) {
has_scheduled_database_messages = true;
break;
}
}
set_dialog_has_scheduled_database_messages(d->dialog_id, has_scheduled_database_messages);
}
if (d->scheduled_messages_sync_generation != scheduled_messages_sync_generation_) {
vector<uint64> numbers;
for (auto &message_id : message_ids) {
if (!message_id.is_scheduled_server()) {
continue;
}
numbers.push_back(message_id.get_scheduled_server_message_id().get());
const Message *m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->message_id.get_scheduled_server_message_id() == message_id.get_scheduled_server_message_id());
numbers.push_back(m->edit_date);
numbers.push_back(m->date);
}
auto hash = get_vector_hash(numbers);
if (!force && (d->has_scheduled_server_messages ||
(d->scheduled_messages_sync_generation == 0 && !G()->parameters().use_message_db))) {
load_dialog_scheduled_messages(dialog_id, false, hash, std::move(promise));
return {};
}
load_dialog_scheduled_messages(dialog_id, false, hash, Promise<Unit>());
}
if (!ignore_result) {
d->sent_scheduled_messages = true;
}
promise.set_value(Unit());
return message_ids;
}
void MessagesManager::load_dialog_scheduled_messages(DialogId dialog_id, bool from_database, int64 hash,
Promise<Unit> &&promise) {
if (G()->parameters().use_message_db && from_database) {
LOG(INFO) << "Load scheduled messages from database in " << dialog_id;
auto &queries = load_scheduled_messages_from_database_queries_[dialog_id];
queries.push_back(std::move(promise));
if (queries.size() == 1) {
G()->td_db()->get_message_db_async()->get_scheduled_messages(
dialog_id, 1000,
PromiseCreator::lambda([dialog_id, actor_id = actor_id(this)](vector<MessageDbDialogMessage> messages) {
send_closure(actor_id, &MessagesManager::on_get_scheduled_messages_from_database, dialog_id,
std::move(messages));
}));
}
} else {
td_->create_handler<GetAllScheduledMessagesQuery>(std::move(promise))
->send(dialog_id, hash, scheduled_messages_sync_generation_);
}
}
void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id,
vector<MessageDbDialogMessage> &&messages) {
if (G()->close_flag()) {
auto it = load_scheduled_messages_from_database_queries_.find(dialog_id);
CHECK(it != load_scheduled_messages_from_database_queries_.end());
CHECK(!it->second.empty());
auto promises = std::move(it->second);
load_scheduled_messages_from_database_queries_.erase(it);
fail_promises(promises, Global::request_aborted_error());
return;
}
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
d->has_loaded_scheduled_messages_from_database = true;
LOG(INFO) << "Receive " << messages.size() << " scheduled messages from database in " << dialog_id;
Dependencies dependencies;
vector<MessageId> added_message_ids;
for (auto &message_slice : messages) {
auto message = parse_message(d, message_slice.message_id, message_slice.data, true);
if (message == nullptr) {
continue;
}
message->from_database = true;
if (get_message(d, message->message_id) != nullptr) {
continue;
}
bool need_update = false;
Message *m = add_scheduled_message_to_dialog(d, std::move(message), false, &need_update,
"on_get_scheduled_messages_from_database");
if (m != nullptr) {
add_message_dependencies(dependencies, m);
added_message_ids.push_back(m->message_id);
}
}
dependencies.resolve_force(td_, "on_get_scheduled_messages_from_database");
// for (auto message_id : added_message_ids) {
// send_update_new_message(d, get_message(d, message_id));
// }
send_update_chat_has_scheduled_messages(d, false);
auto it = load_scheduled_messages_from_database_queries_.find(dialog_id);
CHECK(it != load_scheduled_messages_from_database_queries_.end());
CHECK(!it->second.empty());
auto promises = std::move(it->second);
load_scheduled_messages_from_database_queries_.erase(it);
set_promises(promises);
}
Result<td_api::object_ptr<td_api::availableReactions>> MessagesManager::get_message_available_reactions(
FullMessageId full_message_id, int32 row_size) {
if (row_size < 5 || row_size > 25) {
row_size = 8;
}
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "get_message_available_reactions");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
auto available_reactions = get_message_available_reactions(d, m, false);
bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
bool show_premium = is_premium;
auto recent_reactions = get_recent_reactions(td_);
auto top_reactions = get_top_reactions(td_);
auto active_reactions = get_message_active_reactions(d, m);
LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions
<< " and recent reactions " << recent_reactions;
if (active_reactions.allow_custom_ && active_reactions.allow_all_) {
for (auto &reaction : recent_reactions) {
if (is_custom_reaction(reaction)) {
show_premium = true;
}
}
for (auto &reaction : top_reactions) {
if (is_custom_reaction(reaction)) {
show_premium = true;
}
}
}
FlatHashSet<string> all_available_reactions;
for (const auto &reaction : available_reactions.reactions_) {
CHECK(!reaction.empty());
all_available_reactions.insert(reaction);
}
vector<td_api::object_ptr<td_api::availableReaction>> top_reaction_objects;
vector<td_api::object_ptr<td_api::availableReaction>> recent_reaction_objects;
vector<td_api::object_ptr<td_api::availableReaction>> popular_reaction_objects;
vector<td_api::object_ptr<td_api::availableReaction>> last_reaction_objects;
FlatHashSet<string> added_custom_reactions;
auto add_reactions = [&](vector<td_api::object_ptr<td_api::availableReaction>> &reaction_objects,
const vector<string> &reactions) {
for (auto &reaction : reactions) {
if (all_available_reactions.erase(reaction) != 0) {
// add available reaction
if (is_custom_reaction(reaction)) {
added_custom_reactions.insert(reaction);
}
reaction_objects.push_back(
td_api::make_object<td_api::availableReaction>(get_reaction_type_object(reaction), false));
} else if (is_custom_reaction(reaction) && available_reactions.allow_custom_ &&
added_custom_reactions.insert(reaction).second) {
// add implicitly available custom reaction
reaction_objects.push_back(
td_api::make_object<td_api::availableReaction>(get_reaction_type_object(reaction), !is_premium));
} else {
// skip the reaction
}
}
};
if (show_premium) {
if (top_reactions.size() > 2 * static_cast<size_t>(row_size)) {
top_reactions.resize(2 * static_cast<size_t>(row_size));
}
add_reactions(top_reaction_objects, top_reactions);
if (!recent_reactions.empty()) {
add_reactions(recent_reaction_objects, recent_reactions);
}
} else {
add_reactions(top_reaction_objects, top_reactions);
}
add_reactions(last_reaction_objects, active_reactions_);
add_reactions(last_reaction_objects, available_reactions.reactions_);
if (show_premium) {
if (recent_reactions.empty()) {
popular_reaction_objects = std::move(last_reaction_objects);
} else {
auto max_objects = 10 * static_cast<size_t>(row_size);
if (recent_reaction_objects.size() + last_reaction_objects.size() > max_objects) {
if (last_reaction_objects.size() < max_objects) {
recent_reaction_objects.resize(max_objects - last_reaction_objects.size());
} else {
recent_reaction_objects.clear();
}
}
append(recent_reaction_objects, std::move(last_reaction_objects));
}
} else {
append(top_reaction_objects, std::move(last_reaction_objects));
}
CHECK(all_available_reactions.empty());
return td_api::make_object<td_api::availableReactions>(
std::move(top_reaction_objects), std::move(recent_reaction_objects), std::move(popular_reaction_objects),
available_reactions.allow_custom_);
}
ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m,
bool dissalow_custom_for_non_premium) {
CHECK(d != nullptr);
CHECK(m != nullptr);
auto active_reactions = get_message_active_reactions(d, m);
if (!m->message_id.is_valid() || !m->message_id.is_server() || active_reactions.empty()) {
return {};
}
bool can_use_reactions = true;
if (d->dialog_id.get_type() == DialogType::Channel) {
auto channel_id = d->dialog_id.get_channel_id();
if (td_->contacts_manager_->is_megagroup_channel(channel_id) &&
!td_->contacts_manager_->get_channel_status(channel_id).is_member() &&
can_send_message(d->dialog_id).is_error()) {
can_use_reactions = false;
}
}
int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11);
bool can_add_new_reactions =
m->reactions == nullptr || static_cast<int64>(m->reactions->reactions_.size()) < reactions_uniq_max;
if (!can_use_reactions || !can_add_new_reactions) {
active_reactions = ChatReactions();
}
if (active_reactions.allow_all_) {
active_reactions.reactions_ = active_reactions_;
active_reactions.allow_all_ = false;
}
if (m->reactions != nullptr) {
for (const auto &reaction : m->reactions->reactions_) {
// an already used reaction can be added if it is an active reaction
const string &reaction_str = reaction.get_reaction();
if (can_use_reactions && is_active_reaction(reaction_str, active_reaction_pos_) &&
!td::contains(active_reactions.reactions_, reaction_str)) {
active_reactions.reactions_.push_back(reaction_str);
}
}
}
if (dissalow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) {
active_reactions.allow_custom_ = false;
}
return active_reactions;
}
void MessagesManager::add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big,
bool add_to_recent, Promise<Unit> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "add_message_reaction");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
Message *m = get_message_force(d, full_message_id.get_message_id(), "add_message_reaction");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!get_message_available_reactions(d, m, true).is_allowed_reaction(reaction)) {
return promise.set_error(Status::Error(400, "The reaction isn't available for the message"));
}
bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m);
if (m->reactions == nullptr) {
m->reactions = make_unique<MessageReactions>();
m->reactions->can_get_added_reactions_ = have_recent_choosers && dialog_id.get_type() != DialogType::User;
m->available_reactions_generation = d->available_reactions_generation;
}
if (!m->reactions->add_reaction(reaction, is_big, get_my_dialog_id(), have_recent_choosers)) {
return promise.set_value(Unit());
}
if (add_to_recent) {
add_recent_reaction(td_, reaction);
}
set_message_reactions(d, m, is_big, add_to_recent, std::move(promise));
}
void MessagesManager::remove_message_reaction(FullMessageId full_message_id, string reaction, Promise<Unit> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "remove_message_reaction");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
Message *m = get_message_force(d, full_message_id.get_message_id(), "remove_message_reaction");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (reaction.empty()) {
return promise.set_error(Status::Error(400, "Invalid reaction specified"));
}
bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m);
if (m->reactions == nullptr || !m->reactions->remove_reaction(reaction, get_my_dialog_id(), have_recent_choosers)) {
return promise.set_value(Unit());
}
set_message_reactions(d, m, false, false, std::move(promise));
}
void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent,
Promise<Unit> &&promise) {
CHECK(m->reactions != nullptr);
m->reactions->sort_reactions(active_reaction_pos_);
LOG(INFO) << "Update message reactions to " << *m->reactions;
FullMessageId full_message_id{d->dialog_id, m->message_id};
pending_reactions_[full_message_id].query_count++;
send_update_message_interaction_info(d->dialog_id, m);
on_message_changed(d, m, true, "set_message_reactions");
// TODO cancel previous queries, log event
auto query_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
send_closure(actor_id, &MessagesManager::on_set_message_reactions, full_message_id, std::move(result),
std::move(promise));
});
send_message_reaction(td_, full_message_id, m->reactions->get_chosen_reactions(), is_big, add_to_recent,
std::move(query_promise));
}
void MessagesManager::on_set_message_reactions(FullMessageId full_message_id, Result<Unit> result,
Promise<Unit> promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
bool need_reload = result.is_error();
auto it = pending_reactions_.find(full_message_id);
CHECK(it != pending_reactions_.end());
if (--it->second.query_count == 0) {
// need_reload |= it->second.was_updated;
pending_reactions_.erase(it);
}
if (!have_message_force(full_message_id, "on_set_message_reaction")) {
return promise.set_value(Unit());
}
if (need_reload) {
queue_message_reactions_reload(full_message_id);
}
promise.set_result(std::move(result));
}
void MessagesManager::get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, offset = std::move(offset),
limit, promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
if (r_dc_id.is_error()) {
return promise.set_error(r_dc_id.move_as_error());
}
send_closure(actor_id, &MessagesManager::send_get_message_public_forwards_query, r_dc_id.move_as_ok(),
full_message_id, std::move(offset), limit, std::move(promise));
});
td_->contacts_manager_->get_channel_statistics_dc_id(full_message_id.get_dialog_id(), false,
std::move(dc_id_promise));
}
void MessagesManager::send_get_message_public_forwards_query(
DcId dc_id, FullMessageId full_message_id, string offset, int32 limit,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "send_get_message_public_forwards_query");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "send_get_message_public_forwards_query");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (m->view_count == 0 || m->forward_info != nullptr || m->had_forward_info || m->message_id.is_scheduled() ||
!m->message_id.is_server()) {
return promise.set_error(Status::Error(400, "Message forwards are inaccessible"));
}
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
int32 offset_date = std::numeric_limits<int32>::max();
DialogId offset_dialog_id;
ServerMessageId offset_message_id;
if (!offset.empty()) {
auto parts = full_split(offset, ',');
if (parts.size() != 3) {
return promise.set_error(Status::Error(400, "Invalid offset specified"));
}
auto r_offset_date = to_integer_safe<int32>(parts[0]);
auto r_offset_dialog_id = to_integer_safe<int64>(parts[1]);
auto r_offset_message_id = to_integer_safe<int32>(parts[2]);
if (r_offset_date.is_error() || r_offset_dialog_id.is_error() || r_offset_message_id.is_error()) {
return promise.set_error(Status::Error(400, "Invalid offset specified"));
}
offset_date = r_offset_date.ok();
offset_dialog_id = DialogId(r_offset_dialog_id.ok());
offset_message_id = ServerMessageId(r_offset_message_id.ok());
}
td_->create_handler<GetMessagePublicForwardsQuery>(std::move(promise))
->send(dc_id, full_message_id, offset_date, offset_dialog_id, offset_message_id, limit);
}
Result<int32> MessagesManager::get_message_schedule_date(
td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state) {
if (scheduling_state == nullptr) {
return 0;
}
switch (scheduling_state->get_id()) {
case td_api::messageSchedulingStateSendWhenOnline::ID: {
auto send_date = SCHEDULE_WHEN_ONLINE_DATE;
return send_date;
}
case td_api::messageSchedulingStateSendAtDate::ID: {
auto send_at_date = td_api::move_object_as<td_api::messageSchedulingStateSendAtDate>(scheduling_state);
auto send_date = send_at_date->send_date_;
if (send_date <= 0) {
return Status::Error(400, "Invalid send date specified");
}
if (send_date <= G()->unix_time() + 10) {
return 0;
}
if (send_date - G()->unix_time() > 367 * 86400) {
return Status::Error(400, "Send date is too far in the future");
}
return send_date;
}
default:
UNREACHABLE();
return 0;
}
}
tl_object_ptr<td_api::MessageSendingState> MessagesManager::get_message_sending_state_object(const Message *m) const {
CHECK(m != nullptr);
if (m->message_id.is_yet_unsent()) {
return td_api::make_object<td_api::messageSendingStatePending>();
}
if (m->is_failed_to_send) {
auto can_retry = can_resend_message(m);
auto need_another_sender =
can_retry && m->send_error_code == 400 && m->send_error_message == CSlice("SEND_AS_PEER_INVALID");
return td_api::make_object<td_api::messageSendingStateFailed>(m->send_error_code, m->send_error_message, can_retry,
need_another_sender,
max(m->try_resend_at - Time::now(), 0.0));
}
return nullptr;
}
tl_object_ptr<td_api::MessageSchedulingState> MessagesManager::get_message_scheduling_state_object(int32 send_date) {
if (send_date == SCHEDULE_WHEN_ONLINE_DATE) {
return td_api::make_object<td_api::messageSchedulingStateSendWhenOnline>();
}
return td_api::make_object<td_api::messageSchedulingStateSendAtDate>(send_date);
}
td_api::object_ptr<td_api::message> MessagesManager::get_dialog_event_log_message_object(
DialogId dialog_id, tl_object_ptr<telegram_api::Message> &&message, DialogId &sender_dialog_id) {
auto dialog_message = create_message(parse_telegram_api_message(std::move(message), false, "dialog_event_log"),
dialog_id.get_type() == DialogType::Channel);
const Message *m = dialog_message.second.get();
if (m == nullptr || dialog_message.first != dialog_id) {
LOG(ERROR) << "Failed to create event log message in " << dialog_id;
return nullptr;
}
sender_dialog_id = get_message_sender(m);
auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id,
"get_dialog_event_log_message_object");
auto forward_info = get_message_forward_info_object(m->forward_info);
auto interaction_info = get_message_interaction_info_object(dialog_id, m);
auto can_be_saved = can_save_message(dialog_id, m);
auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id");
auto edit_date = m->hide_edit_date ? 0 : m->edit_date;
auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup);
auto content = get_message_content_object(m->content.get(), td_, dialog_id, 0, false, true,
get_message_own_max_media_timestamp(m));
return td_api::make_object<td_api::message>(
m->message_id.get(), std::move(sender), dialog_id.get(), nullptr, nullptr, m->is_outgoing, m->is_pinned, false,
false, can_be_saved, false, false, false, false, false, false, false, false, true, m->is_channel_post,
m->is_topic_message, false, m->date, edit_date, std::move(forward_info), std::move(interaction_info), Auto(), 0,
0, 0, 0, 0.0, 0.0, via_bot_user_id, m->author_signature, 0,
get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup));
}
tl_object_ptr<td_api::message> MessagesManager::get_message_object(FullMessageId full_message_id, const char *source) {
return get_message_object(full_message_id.get_dialog_id(), get_message_force(full_message_id, source), source);
}
tl_object_ptr<td_api::message> MessagesManager::get_message_object(DialogId dialog_id, const Message *m,
const char *source) const {
if (m == nullptr) {
return nullptr;
}
LOG_CHECK(have_dialog(dialog_id)) << source;
m->is_update_sent = true;
auto sending_state = get_message_sending_state_object(m);
bool can_delete = can_delete_message(dialog_id, m);
bool is_scheduled = m->message_id.is_scheduled();
DialogId my_dialog_id = get_my_dialog_id();
bool can_delete_for_self = false;
bool can_delete_for_all_users = can_delete && can_revoke_message(dialog_id, m);
if (can_delete) {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
// TODO allow to delete yet unsent message just for self
can_delete_for_self = !m->message_id.is_yet_unsent() || dialog_id == my_dialog_id;
break;
case DialogType::Channel:
case DialogType::SecretChat:
can_delete_for_self = !can_delete_for_all_users;
break;
case DialogType::None:
default:
UNREACHABLE();
}
}
if (is_scheduled) {
can_delete_for_self = (dialog_id == my_dialog_id);
can_delete_for_all_users = !can_delete_for_self;
}
bool is_outgoing = m->is_outgoing;
if (dialog_id == my_dialog_id) {
// in Saved Messages all non-forwarded messages must be outgoing
// a forwarded message is outgoing, only if it doesn't have from_dialog_id and its sender isn't hidden
// i.e. a message is incoming only if it's a forwarded message with known from_dialog_id or with a hidden sender
auto forward_info = m->forward_info.get();
is_outgoing = is_scheduled || forward_info == nullptr ||
(!forward_info->from_dialog_id.is_valid() && !is_forward_info_sender_hidden(forward_info));
}
double ttl_expires_in = m->ttl_expires_at != 0 ? clamp(m->ttl_expires_at - Time::now(), 1e-3, m->ttl - 1e-3) : m->ttl;
double auto_delete_in =
m->ttl_period == 0 ? 0.0 : clamp(m->date + m->ttl_period - G()->server_time(), 1e-3, m->ttl_period - 1e-3);
auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, source);
auto scheduling_state = is_scheduled ? get_message_scheduling_state_object(m->date) : nullptr;
auto forward_info = get_message_forward_info_object(m->forward_info);
auto interaction_info = get_message_interaction_info_object(dialog_id, m);
auto unread_reactions = get_unread_reactions_object(dialog_id, m);
auto can_be_saved = can_save_message(dialog_id, m);
auto can_be_edited = can_edit_message(dialog_id, m, false, td_->auth_manager_->is_bot());
auto can_be_forwarded = can_be_saved && can_forward_message(dialog_id, m);
auto can_get_added_reactions = m->reactions != nullptr && m->reactions->can_get_added_reactions_;
auto can_get_statistics = can_get_message_statistics(dialog_id, m);
auto can_get_message_thread = get_top_thread_full_message_id(dialog_id, m, false).is_ok();
auto can_get_viewers = can_get_message_viewers(dialog_id, m).is_ok();
auto can_get_media_timestamp_links = can_get_media_timestamp_link(dialog_id, m).is_ok();
auto can_report_reactions = can_report_message_reactions(dialog_id, m);
auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id");
auto reply_to_message_id = m->reply_to_message_id.get();
auto reply_in_dialog_id =
reply_to_message_id == 0 ? DialogId() : (m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id);
auto top_thread_message_id = m->top_thread_message_id.get();
auto date = is_scheduled ? 0 : m->date;
auto edit_date = m->hide_edit_date ? 0 : m->edit_date;
auto is_pinned = is_scheduled ? false : m->is_pinned;
auto has_timestamped_media = reply_to_message_id == 0 || m->max_own_media_timestamp >= 0;
auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup);
auto live_location_date = m->is_failed_to_send ? 0 : m->date;
auto skip_bot_commands = need_skip_bot_commands(dialog_id, m);
auto max_media_timestamp = get_message_max_media_timestamp(m);
auto content = get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret,
skip_bot_commands, max_media_timestamp);
if (m->is_topic_message && reply_in_dialog_id == dialog_id && reply_to_message_id == top_thread_message_id &&
!td_->auth_manager_->is_bot()) {
reply_in_dialog_id = DialogId();
reply_to_message_id = 0;
}
return td_api::make_object<td_api::message>(
m->message_id.get(), std::move(sender), dialog_id.get(), std::move(sending_state), std::move(scheduling_state),
is_outgoing, is_pinned, can_be_edited, can_be_forwarded, can_be_saved, can_delete_for_self,
can_delete_for_all_users, can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_viewers,
can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post,
m->is_topic_message, m->contains_unread_mention, date, edit_date, std::move(forward_info),
std::move(interaction_info), std::move(unread_reactions), reply_in_dialog_id.get(), reply_to_message_id,
top_thread_message_id, m->ttl, ttl_expires_in, auto_delete_in, via_bot_user_id, m->author_signature,
m->media_album_id, get_restriction_reason_description(m->restriction_reasons), std::move(content),
std::move(reply_markup));
}
tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id,
const vector<MessageId> &message_ids,
bool skip_not_found, const char *source) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto message_objects = transform(message_ids, [this, dialog_id, d, source](MessageId message_id) {
return get_message_object(dialog_id, get_message_force(d, message_id, source), source);
});
return get_messages_object(total_count, std::move(message_objects), skip_not_found);
}
tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count,
const vector<FullMessageId> &full_message_ids,
bool skip_not_found, const char *source) {
auto message_objects = transform(full_message_ids, [this, source](FullMessageId full_message_id) {
return get_message_object(full_message_id, source);
});
return get_messages_object(total_count, std::move(message_objects), skip_not_found);
}
tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count,
vector<tl_object_ptr<td_api::message>> &&messages,
bool skip_not_found) {
auto message_count = narrow_cast<int32>(messages.size());
if (total_count < message_count) {
if (total_count != -1) {
LOG(ERROR) << "Have wrong total_count = " << total_count << ", while having " << message_count << " messages";
}
total_count = message_count;
}
if (skip_not_found && td::remove(messages, nullptr)) {
total_count -= message_count - static_cast<int32>(messages.size());
}
return td_api::make_object<td_api::messages>(total_count, std::move(messages));
}
bool MessagesManager::is_anonymous_administrator(DialogId dialog_id, string *author_signature) const {
CHECK(dialog_id.is_valid());
if (is_broadcast_channel(dialog_id)) {
return true;
}
if (td_->auth_manager_->is_bot()) {
return false;
}
if (dialog_id.get_type() != DialogType::Channel) {
return false;
}
auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
if (!status.is_anonymous()) {
return false;
}
if (author_signature != nullptr) {
*author_signature = status.get_rank();
}
return true;
}
bool MessagesManager::get_dialog_silent_send_message(DialogId dialog_id) const {
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
return d->notification_settings.silent_send_message;
}
DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const {
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
return d->default_send_message_as_dialog_id;
}
int64 MessagesManager::generate_new_random_id(const Dialog *d) {
int64 random_id;
do {
random_id = Random::secure_int64();
} while (random_id == 0 || being_sent_messages_.count(random_id) > 0 ||
d->random_id_to_message_id.count(random_id) > 0);
return random_id;
}
unique_ptr<MessagesManager::Message> MessagesManager::create_message_to_send(
Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options,
unique_ptr<MessageContent> &&content, bool suppress_reply_info, unique_ptr<MessageForwardInfo> forward_info,
bool is_copy, DialogId send_as_dialog_id) const {
CHECK(d != nullptr);
CHECK(content != nullptr);
bool is_scheduled = options.schedule_date != 0;
DialogId dialog_id = d->dialog_id;
auto dialog_type = dialog_id.get_type();
auto my_id = td_->contacts_manager_->get_my_id();
auto m = make_unique<Message>();
bool is_channel_post = is_broadcast_channel(dialog_id);
if (is_channel_post) {
// sender of the post can be hidden
if (!is_scheduled && td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) {
m->author_signature = td_->contacts_manager_->get_user_title(my_id);
}
m->sender_dialog_id = dialog_id;
} else {
if (send_as_dialog_id.is_valid()) {
if (send_as_dialog_id.get_type() == DialogType::User) {
m->sender_user_id = send_as_dialog_id.get_user_id();
} else {
m->sender_dialog_id = send_as_dialog_id;
}
} else if (d->default_send_message_as_dialog_id.is_valid()) {
if (d->default_send_message_as_dialog_id.get_type() == DialogType::User) {
m->sender_user_id = my_id;
} else {
m->sender_dialog_id = d->default_send_message_as_dialog_id;
}
m->has_explicit_sender = true;
} else {
if (is_anonymous_administrator(dialog_id, &m->author_signature)) {
m->sender_dialog_id = dialog_id;
} else {
m->sender_user_id = my_id;
}
}
}
m->send_date = G()->unix_time();
m->date = is_scheduled ? options.schedule_date : m->send_date;
m->reply_to_message_id = reply_to_message_id;
m->top_thread_message_id = top_thread_message_id;
if (reply_to_message_id.is_valid()) {
const Message *reply_m = get_message(d, reply_to_message_id);
if (reply_m != nullptr && reply_m->top_thread_message_id.is_valid()) {
m->top_thread_message_id = reply_m->top_thread_message_id;
}
if (reply_m != nullptr && m->top_thread_message_id.is_valid()) {
m->is_topic_message = reply_m->is_topic_message;
}
} else if (m->top_thread_message_id.is_valid()) {
const Message *top_m = get_message(d, m->top_thread_message_id);
if (top_m != nullptr) {
m->is_topic_message = top_m->is_topic_message;
}
}
m->is_channel_post = is_channel_post;
m->is_outgoing = is_scheduled || dialog_id != DialogId(my_id);
m->from_background = options.from_background;
m->update_stickersets_order = options.update_stickersets_order;
m->noforwards = options.protect_content;
m->view_count = is_channel_post && !is_scheduled ? 1 : 0;
m->forward_count = 0;
if ([&] {
if (suppress_reply_info) {
return false;
}
if (is_scheduled) {
return false;
}
if (dialog_type != DialogType::Channel) {
return false;
}
if (td_->auth_manager_->is_bot()) {
return false;
}
if (is_channel_post) {
return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id());
}
return !reply_to_message_id.is_valid();
}()) {
m->reply_info.reply_count_ = 0;
if (is_channel_post) {
auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id());
if (linked_channel_id.is_valid()) {
m->reply_info.is_comment_ = true;
m->reply_info.channel_id_ = linked_channel_id;
}
}
}
m->content = std::move(content);
m->forward_info = std::move(forward_info);
m->is_copy = is_copy || m->forward_info != nullptr;
if (td_->auth_manager_->is_bot() || options.disable_notification ||
td_->option_manager_->get_option_boolean("ignore_default_disable_notification")) {
m->disable_notification = options.disable_notification;
} else {
m->disable_notification = d->notification_settings.silent_send_message;
}
if (dialog_type == DialogType::SecretChat) {
CHECK(!is_scheduled);
m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id());
if (is_service_message_content(m->content->get_type())) {
m->ttl = 0;
}
m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
}
if ((reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled()) &&
(dialog_type == DialogType::SecretChat || reply_to_message_id.is_yet_unsent())) {
// the message was forcely preloaded in get_reply_to_message_id
auto *reply_to_message = get_message(d, reply_to_message_id);
if (reply_to_message == nullptr) {
m->reply_to_message_id = MessageId();
} else {
m->reply_to_random_id = reply_to_message->random_id;
}
}
return m;
}
MessagesManager::Message *MessagesManager::get_message_to_send(
Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options,
unique_ptr<MessageContent> &&content, bool *need_update_dialog_pos, bool suppress_reply_info,
unique_ptr<MessageForwardInfo> forward_info, bool is_copy, DialogId send_as_dialog_id) {
d->was_opened = true;
auto message = create_message_to_send(d, top_thread_message_id, reply_to_message_id, options, std::move(content),
suppress_reply_info, std::move(forward_info), is_copy, send_as_dialog_id);
MessageId message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date)
: get_next_yet_unsent_message_id(d);
set_message_id(message, message_id);
message->have_previous = true;
message->have_next = true;
message->random_id = generate_new_random_id(d);
bool need_update = false;
CHECK(have_input_peer(d->dialog_id, AccessRights::Read));
auto result =
add_message_to_dialog(d, std::move(message), true, &need_update, need_update_dialog_pos, "send message");
LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_;
if (result->message_id.is_scheduled()) {
send_update_chat_has_scheduled_messages(d, false);
}
if (options.update_stickersets_order && !td_->auth_manager_->is_bot()) {
move_message_content_sticker_set_to_top(td_, result->content.get());
}
return result;
}
int64 MessagesManager::begin_send_message(DialogId dialog_id, const Message *m) {
LOG(INFO) << "Begin to send " << FullMessageId(dialog_id, m->message_id) << " with random_id = " << m->random_id;
CHECK(m->random_id != 0);
CHECK(m->message_id.is_yet_unsent());
bool is_inserted = being_sent_messages_.emplace(m->random_id, FullMessageId(dialog_id, m->message_id)).second;
CHECK(is_inserted);
return m->random_id;
}
Status MessagesManager::can_send_message(DialogId dialog_id) const {
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return Status::Error(400, "Have no write access to the chat");
}
if (dialog_id.get_type() == DialogType::Channel) {
auto channel_id = dialog_id.get_channel_id();
auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
switch (channel_type) {
case ChannelType::Unknown:
case ChannelType::Megagroup:
if (!channel_status.can_send_messages()) {
return Status::Error(400, "Have no rights to send a message");
}
break;
case ChannelType::Broadcast: {
if (!channel_status.can_post_messages()) {
return Status::Error(400, "Need administrator rights in the channel chat");
}
break;
}
default:
UNREACHABLE();
}
}
return Status::OK();
}
MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId message_id) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return MessageId();
}
if (message_id.is_yet_unsent()) {
// it is possible that user tries to do something with an already sent message by its temporary id
// we need to use real message in this case and transparently replace message_id
auto it = d->yet_unsent_message_id_to_persistent_message_id.find(message_id);
if (it != d->yet_unsent_message_id_to_persistent_message_id.end()) {
return it->second;
}
}
return message_id;
}
MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId top_thread_message_id, MessageId message_id,
bool for_draft) {
CHECK(d != nullptr);
if (top_thread_message_id.is_valid() && !have_message_force(d, top_thread_message_id, "get_reply_to_message_id 1")) {
LOG(INFO) << "Have reply to " << message_id << " in the thread of unknown " << top_thread_message_id;
}
if (!message_id.is_valid()) {
if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() &&
top_thread_message_id.is_server()) {
return top_thread_message_id;
}
return MessageId();
}
message_id = get_persistent_message_id(d, message_id);
const Message *m = get_message_force(d, message_id, "get_reply_to_message_id 2");
if (m == nullptr || m->message_id.is_yet_unsent() ||
(m->message_id.is_local() && d->dialog_id.get_type() != DialogType::SecretChat)) {
if (message_id.is_server() && d->dialog_id.get_type() != DialogType::SecretChat &&
message_id > d->last_new_message_id && message_id <= d->max_notification_message_id) {
// allow to reply yet unreceived server message
return message_id;
}
if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) {
return top_thread_message_id;
}
// TODO local replies to local messages can be allowed
// TODO replies to yet unsent messages can be allowed with special handling of them on application restart
return MessageId();
}
return m->message_id;
}
void MessagesManager::fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id,
DialogId reply_in_dialog_id,
MessageId &reply_to_message_id) const {
if (!reply_to_message_id.is_valid()) {
if (reply_to_message_id.is_valid_scheduled()) {
CHECK(message_id.is_scheduled());
CHECK(reply_in_dialog_id == DialogId());
if (message_id == reply_to_message_id) {
LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id;
reply_to_message_id = MessageId();
}
return;
}
if (reply_to_message_id != MessageId()) {
LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id;
reply_to_message_id = MessageId();
}
return;
}
if (!message_id.is_scheduled() && !reply_in_dialog_id.is_valid() && reply_to_message_id >= message_id) {
if (!has_qts_messages(dialog_id)) {
LOG(ERROR) << "Receive reply to wrong " << reply_to_message_id << " in " << message_id << " in " << dialog_id;
}
reply_to_message_id = MessageId();
}
}
vector<FileId> MessagesManager::get_message_file_ids(const Message *m) const {
CHECK(m != nullptr);
return get_message_content_file_ids(m->content.get(), td_);
}
void MessagesManager::cancel_upload_message_content_files(const MessageContent *content) {
auto file_id = get_message_content_upload_file_id(content);
// always cancel file upload, it should be a no-op in the worst case
if (being_uploaded_files_.erase(file_id) || file_id.is_valid()) {
cancel_upload_file(file_id, "cancel_upload_message_content_files");
}
file_id = get_message_content_thumbnail_file_id(content, td_);
if (being_uploaded_thumbnails_.erase(file_id) || file_id.is_valid()) {
cancel_upload_file(file_id, "cancel_upload_message_content_files");
}
}
void MessagesManager::cancel_upload_file(FileId file_id, const char *source) {
// send the request later so they doesn't interfere with other actions
// for example merge, supposed to happen soon, can auto-cancel the upload
LOG(INFO) << "Cancel upload of file " << file_id << " from " << source;
send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id);
}
void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) {
CHECK(m != nullptr);
CHECK(m->content != nullptr);
CHECK(m->message_id.is_valid() || m->message_id.is_valid_scheduled());
CHECK(m->message_id.is_yet_unsent());
LOG(INFO) << "Cancel send message query for " << m->message_id;
cancel_upload_message_content_files(m->content.get());
CHECK(m->edited_content == nullptr);
if (!m->send_query_ref.empty()) {
LOG(INFO) << "Cancel send query for " << m->message_id;
cancel_query(m->send_query_ref);
m->send_query_ref = NetQueryRef();
}
if (m->send_message_log_event_id != 0) {
LOG(INFO) << "Delete send message log event for " << m->message_id;
binlog_erase(G()->td_db()->get_binlog(), m->send_message_log_event_id);
m->send_message_log_event_id = 0;
}
if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) {
auto it = replied_by_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id});
CHECK(it != replied_by_yet_unsent_messages_.end());
it->second--;
CHECK(it->second >= 0);
if (it->second == 0) {
replied_by_yet_unsent_messages_.erase(it);
}
}
if ((m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_valid_scheduled()) &&
m->reply_to_message_id.is_yet_unsent()) {
auto it = replied_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id});
CHECK(it != replied_yet_unsent_messages_.end());
size_t erased_count = it->second.erase(m->message_id);
CHECK(erased_count > 0);
if (it->second.empty()) {
replied_yet_unsent_messages_.erase(it);
}
}
{
auto it = replied_yet_unsent_messages_.find({dialog_id, m->message_id});
if (it != replied_yet_unsent_messages_.end()) {
for (auto message_id : it->second) {
auto replied_m = get_message({dialog_id, message_id});
CHECK(replied_m != nullptr);
CHECK(replied_m->reply_to_message_id == m->message_id);
unregister_message_reply(dialog_id, replied_m);
replied_m->reply_to_message_id = replied_m->top_thread_message_id;
replied_m->reply_to_random_id = 0;
register_message_reply(dialog_id, replied_m);
}
replied_yet_unsent_messages_.erase(it);
}
}
if (m->media_album_id != 0) {
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
m->message_id, Status::OK());
}
if (!m->message_id.is_scheduled() && G()->parameters().use_file_db &&
!m->is_copy) { // ResourceManager::Mode::Baseline
auto queue_id = ChainId(dialog_id, m->content->get_type()).get();
if (queue_id & 1) {
auto queue_it = yet_unsent_media_queues_.find(queue_id);
if (queue_it != yet_unsent_media_queues_.end()) {
auto &queue = queue_it->second;
LOG(INFO) << "Delete " << m->message_id << " from queue " << queue_id;
if (queue.erase(m->message_id) != 0) {
if (queue.empty()) {
yet_unsent_media_queues_.erase(queue_it);
} else {
// send later, because do_delete_all_dialog_messages can be called right now
send_closure_later(actor_id(this), &MessagesManager::on_yet_unsent_media_queue_updated, dialog_id);
}
}
}
}
}
}
void MessagesManager::cancel_send_deleted_message(DialogId dialog_id, Message *m, bool is_permanently_deleted) {
CHECK(m != nullptr);
if (m->message_id.is_yet_unsent()) {
cancel_send_message_query(dialog_id, m);
} else if (is_permanently_deleted || !m->message_id.is_scheduled()) {
cancel_edit_message_media(dialog_id, m, "Message was deleted");
}
}
bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing) const {
switch (dialog_id.get_type()) {
case DialogType::User: {
auto user_id = dialog_id.get_user_id();
if (user_id == td_->contacts_manager_->get_my_id()) {
return true;
}
if (is_outgoing && td_->contacts_manager_->is_user_bot(user_id) &&
!td_->contacts_manager_->is_user_support(user_id)) {
return true;
}
return false;
}
case DialogType::Chat:
// TODO auto_read message content and messages sent to group with bots only
return false;
case DialogType::Channel:
return is_outgoing && is_broadcast_channel(dialog_id);
case DialogType::SecretChat:
return false;
case DialogType::None:
return false;
default:
UNREACHABLE();
return false;
}
}
void MessagesManager::add_message_dependencies(Dependencies &dependencies, const Message *m) {
dependencies.add(m->sender_user_id);
dependencies.add_dialog_and_dependencies(m->sender_dialog_id);
dependencies.add_dialog_and_dependencies(m->reply_in_dialog_id);
dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id);
dependencies.add(m->via_bot_user_id);
if (m->forward_info != nullptr) {
dependencies.add(m->forward_info->sender_user_id);
dependencies.add_dialog_and_dependencies(m->forward_info->sender_dialog_id);
dependencies.add_dialog_and_dependencies(m->forward_info->from_dialog_id);
}
for (const auto &replier_min_channel : m->reply_info.replier_min_channels_) {
LOG(INFO) << "Add min replied " << replier_min_channel.first;
td_->contacts_manager_->add_min_channel(replier_min_channel.first, replier_min_channel.second);
}
for (auto recent_replier_dialog_id : m->reply_info.recent_replier_dialog_ids_) {
// don't load the dialog itself
// it will be created in get_message_reply_info_object if needed
dependencies.add_dialog_dependencies(recent_replier_dialog_id);
}
if (m->reactions != nullptr) {
m->reactions->add_min_channels(td_);
m->reactions->add_dependencies(dependencies);
}
add_message_content_dependencies(dependencies, m->content.get());
add_reply_markup_dependencies(dependencies, m->reply_markup.get());
}
void MessagesManager::get_dialog_send_message_as_dialog_ids(
DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatMessageSenders>> &&promise, bool is_recursive) {
TRY_STATUS_PROMISE(promise, G()->close_status());
const Dialog *d = get_dialog_force(dialog_id, "get_group_call_join_as");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access chat"));
}
if (!d->default_send_message_as_dialog_id.is_valid()) {
return promise.set_value(td_api::make_object<td_api::chatMessageSenders>());
}
CHECK(dialog_id.get_type() == DialogType::Channel);
if (created_public_broadcasts_inited_) {
auto senders = td_api::make_object<td_api::chatMessageSenders>();
if (!created_public_broadcasts_.empty()) {
auto add_sender = [&senders, td = td_](DialogId dialog_id, bool needs_premium) {
auto sender = get_message_sender_object_const(td, dialog_id, "add_sender");
senders->senders_.push_back(td_api::make_object<td_api::chatMessageSender>(std::move(sender), needs_premium));
};
if (is_anonymous_administrator(dialog_id, nullptr)) {
add_sender(dialog_id, false);
} else {
add_sender(get_my_dialog_id(), false);
}
struct Sender {
ChannelId channel_id;
bool needs_premium;
};
std::multimap<int64, Sender> sorted_senders;
bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id());
for (auto channel_id : created_public_broadcasts_) {
int64 score = td_->contacts_manager_->get_channel_participant_count(channel_id);
bool needs_premium = !is_premium && channel_id != linked_channel_id &&
!td_->contacts_manager_->get_channel_is_verified(channel_id);
if (needs_premium) {
score -= static_cast<int64>(1) << 40;
}
if (channel_id == linked_channel_id) {
score += static_cast<int64>(1) << 32;
}
sorted_senders.emplace(-score, Sender{channel_id, needs_premium});
};
for (auto &sender : sorted_senders) {
add_sender(DialogId(sender.second.channel_id), sender.second.needs_premium);
}
}
return promise.set_value(std::move(senders));
}
CHECK(!is_recursive);
auto new_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, promise = std::move(promise)](
Result<td_api::object_ptr<td_api::chats>> &&result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
send_closure_later(actor_id, &MessagesManager::get_dialog_send_message_as_dialog_ids, dialog_id,
std::move(promise), true);
}
});
td_->contacts_manager_->get_created_public_dialogs(PublicDialogType::HasUsername, std::move(new_promise), true);
}
void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dialog_id,
DialogId message_sender_dialog_id,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "set_dialog_default_send_message_as_dialog_id");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!d->default_send_message_as_dialog_id.is_valid()) {
return promise.set_error(Status::Error(400, "Can't change message sender in the chat"));
}
// checked in on_update_dialog_default_send_message_as_dialog_id
CHECK(dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id));
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr);
switch (message_sender_dialog_id.get_type()) {
case DialogType::User:
if (message_sender_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) {
return promise.set_error(Status::Error(400, "Can't send messages as another user"));
}
if (is_anonymous) {
return promise.set_error(Status::Error(400, "Can't send messages as self"));
}
break;
case DialogType::Chat:
case DialogType::Channel:
case DialogType::SecretChat:
if (is_anonymous && dialog_id == message_sender_dialog_id) {
break;
}
if (!is_broadcast_channel(message_sender_dialog_id) ||
td_->contacts_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) {
return promise.set_error(Status::Error(400, "Message sender chat must be a public channel"));
}
break;
default:
return promise.set_error(Status::Error(400, "Invalid message sender specified"));
}
if (!have_input_peer(message_sender_dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access specified message sender chat"));
}
{
auto it = set_typing_query_.find(dialog_id);
if (it != set_typing_query_.end()) {
if (!it->second.empty()) {
cancel_query(it->second);
}
set_typing_query_.erase(it);
}
}
td_->create_handler<SaveDefaultSendAsQuery>(std::move(promise))->send(dialog_id, message_sender_dialog_id);
on_update_dialog_default_send_message_as_dialog_id(dialog_id, message_sender_dialog_id, true);
}
class MessagesManager::SendMessageLogEvent {
public:
DialogId dialog_id;
const Message *m_in;
unique_ptr<Message> m_out;
SendMessageLogEvent() : dialog_id(), m_in(nullptr) {
}
SendMessageLogEvent(DialogId dialog_id, const Message *m) : dialog_id(dialog_id), m_in(m) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id, storer);
td::store(*m_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id, parser);
td::parse(m_out, parser);
}
};
Result<td_api::object_ptr<td_api::message>> MessagesManager::send_message(
DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
tl_object_ptr<td_api::messageSendOptions> &&options, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
if (input_message_content == nullptr) {
return Status::Error(400, "Can't send message without content");
}
Dialog *d = get_dialog_force(dialog_id, "send_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
LOG(INFO) << "Begin to send message to " << dialog_id << " in reply to " << reply_to_message_id;
reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) {
auto input_message = td_api::move_object_as<td_api::inputMessageForwarded>(input_message_content);
TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_)));
copy_options.reply_to_message_id = reply_to_message_id;
TRY_RESULT_ASSIGN(copy_options.reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup)));
return forward_message(dialog_id, top_thread_message_id, DialogId(input_message->from_chat_id_),
MessageId(input_message->message_id_), std::move(options), input_message->in_game_share_,
std::move(copy_options));
}
TRY_STATUS(can_send_message(dialog_id));
TRY_RESULT(message_reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup)));
TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true));
TRY_STATUS(can_use_message_send_options(message_send_options, message_content));
TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
// there must be no errors after get_message_to_send call
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
dup_message_content(td_, dialog_id, message_content.content.get(),
MessageContentDupType::Send, MessageCopyOptions()),
&need_update_dialog_pos, false, nullptr, message_content.via_bot_user_id.is_valid());
m->reply_markup = std::move(message_reply_markup);
m->via_bot_user_id = message_content.via_bot_user_id;
m->disable_web_page_preview = message_content.disable_web_page_preview;
m->clear_draft = message_content.clear_draft;
if (message_content.ttl > 0) {
m->ttl = message_content.ttl;
m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
}
m->send_emoji = std::move(message_content.emoji);
if (m->clear_draft) {
if (top_thread_message_id.is_valid()) {
set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore();
} else {
update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
}
}
save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_message");
}
return get_message_object(dialog_id, m, "send_message");
}
Result<InputMessageContent> MessagesManager::process_input_message_content(
DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
if (input_message_content == nullptr) {
return Status::Error(400, "Can't send message without content");
}
if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) {
// for sendMessageAlbum/editMessageMedia/addLocalMessage
auto input_message = td_api::move_object_as<td_api::inputMessageForwarded>(input_message_content);
TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_)));
if (!copy_options.send_copy) {
return Status::Error(400, "Can't use forwarded message");
}
DialogId from_dialog_id(input_message->from_chat_id_);
Dialog *from_dialog = get_dialog_force(from_dialog_id, "send_message copy");
if (from_dialog == nullptr) {
return Status::Error(400, "Chat to copy message from not found");
}
if (!have_input_peer(from_dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat to copy message from");
}
if (from_dialog_id.get_type() == DialogType::SecretChat) {
return Status::Error(400, "Can't copy message from secret chats");
}
MessageId message_id = get_persistent_message_id(from_dialog, MessageId(input_message->message_id_));
const Message *copied_message = get_message_force(from_dialog, message_id, "process_input_message_content");
if (copied_message == nullptr) {
return Status::Error(400, "Can't find message to copy");
}
if (!can_forward_message(from_dialog_id, copied_message)) {
return Status::Error(400, "Can't copy message");
}
if (!can_save_message(from_dialog_id, copied_message) && !td_->auth_manager_->is_bot()) {
return Status::Error(400, "Message copying is restricted");
}
unique_ptr<MessageContent> content = dup_message_content(td_, dialog_id, copied_message->content.get(),
MessageContentDupType::Copy, std::move(copy_options));
if (content == nullptr) {
return Status::Error(400, "Can't copy message content");
}
return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), false, 0,
UserId(), copied_message->send_emoji);
}
bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_, is_premium));
if (content.ttl < 0 || content.ttl > MAX_PRIVATE_MESSAGE_TTL) {
return Status::Error(400, "Invalid message content TTL specified");
}
if (content.ttl > 0 && dialog_id.get_type() != DialogType::User) {
return Status::Error(400, "Message content TTL can be specified only in private chats");
}
if (dialog_id != DialogId()) {
TRY_STATUS(can_send_message_content(dialog_id, content.content.get(), false, td_));
}
return std::move(content);
}
Result<MessageCopyOptions> MessagesManager::process_message_copy_options(
DialogId dialog_id, tl_object_ptr<td_api::messageCopyOptions> &&options) const {
if (options == nullptr || !options->send_copy_) {
return MessageCopyOptions();
}
MessageCopyOptions result;
result.send_copy = true;
result.replace_caption = options->replace_caption_;
if (result.replace_caption) {
TRY_RESULT_ASSIGN(result.new_caption, get_formatted_text(td_, dialog_id, std::move(options->new_caption_),
td_->auth_manager_->is_bot(), true, false, false));
}
return std::move(result);
}
Result<MessagesManager::MessageSendOptions> MessagesManager::process_message_send_options(
DialogId dialog_id, tl_object_ptr<td_api::messageSendOptions> &&options,
bool allow_update_stickersets_order) const {
MessageSendOptions result;
if (options != nullptr) {
result.disable_notification = options->disable_notification_;
result.from_background = options->from_background_;
if (allow_update_stickersets_order) {
result.update_stickersets_order = options->update_order_of_installed_sticker_sets_;
}
result.protect_content = options->protect_content_;
TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_)));
}
auto dialog_type = dialog_id.get_type();
if (result.schedule_date != 0) {
if (dialog_type == DialogType::SecretChat) {
return Status::Error(400, "Can't schedule messages in secret chats");
}
if (td_->auth_manager_->is_bot()) {
return Status::Error(400, "Bots can't send scheduled messages");
}
}
if (result.schedule_date == SCHEDULE_WHEN_ONLINE_DATE) {
if (dialog_type != DialogType::User) {
return Status::Error(400, "Messages can be scheduled till online only in private chats");
}
if (dialog_id == get_my_dialog_id()) {
return Status::Error(400, "Can't scheduled till online messages in chat with self");
}
}
if (result.protect_content && !td_->auth_manager_->is_bot()) {
result.protect_content = false;
}
return result;
}
Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options,
const unique_ptr<MessageContent> &content, int32 ttl) {
if (options.schedule_date != 0) {
if (ttl > 0) {
return Status::Error(400, "Can't send scheduled self-destructing messages");
}
if (content->get_type() == MessageContentType::LiveLocation) {
return Status::Error(400, "Can't send scheduled live location messages");
}
}
return Status::OK();
}
Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options,
const InputMessageContent &content) {
return can_use_message_send_options(options, content.content, content.ttl);
}
Status MessagesManager::can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id,
MessageId reply_to_message_id) {
if (top_thread_message_id == MessageId()) {
return Status::OK();
}
if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
return Status::Error(400, "Invalid message thread identifier specified");
}
if (d->dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(d->dialog_id)) {
return Status::Error(400, "Chat doesn't have threads");
}
if (reply_to_message_id.is_valid()) {
const Message *reply_m = get_message_force(d, reply_to_message_id, "can_use_top_thread_message_id 1");
if (reply_m != nullptr && top_thread_message_id != reply_m->top_thread_message_id) {
if (reply_m->top_thread_message_id.is_valid() || reply_m->media_album_id == 0) {
return Status::Error(400, "The message to reply is not in the specified message thread");
}
// if the message is in an album and not in the thread, it can be in the album of top_thread_message_id
const Message *top_m = get_message_force(d, top_thread_message_id, "can_use_top_thread_message_id 2");
if (top_m != nullptr &&
(top_m->media_album_id != reply_m->media_album_id || top_m->top_thread_message_id != top_m->message_id)) {
return Status::Error(400, "The message to reply is not in the specified message thread root album");
}
}
}
return Status::OK();
}
int64 MessagesManager::generate_new_media_album_id() {
int64 media_album_id = 0;
do {
media_album_id = Random::secure_int64();
} while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) != 0);
return media_album_id;
}
Result<td_api::object_ptr<td_api::messages>> MessagesManager::send_message_group(
DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
tl_object_ptr<td_api::messageSendOptions> &&options,
vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents, bool only_preview) {
if (input_message_contents.size() > MAX_GROUPED_MESSAGES) {
return Status::Error(400, "Too many messages to send as an album");
}
if (input_message_contents.empty()) {
return Status::Error(400, "There are no messages to send");
}
Dialog *d = get_dialog_force(dialog_id, "send_message_group");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true));
vector<std::pair<unique_ptr<MessageContent>, int32>> message_contents;
std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
for (auto &input_message_content : input_message_contents) {
TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
TRY_STATUS(can_use_message_send_options(message_send_options, message_content));
auto message_content_type = message_content.content->get_type();
if (!is_allowed_media_group_content(message_content_type)) {
return Status::Error(400, "Invalid message content type");
}
message_content_types.insert(message_content_type);
message_contents.emplace_back(std::move(message_content.content), message_content.ttl);
}
if (message_content_types.size() > 1) {
for (auto message_content_type : message_content_types) {
if (is_homogenous_media_group_content(message_content_type)) {
return Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types");
}
}
}
reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
int64 media_album_id = 0;
if (message_contents.size() > 1) {
media_album_id = generate_new_media_album_id();
}
// there must be no errors after get_message_to_send calls
vector<tl_object_ptr<td_api::message>> result;
bool need_update_dialog_pos = false;
for (size_t i = 0; i < message_contents.size(); i++) {
auto &message_content = message_contents[i];
unique_ptr<Message> message;
Message *m;
if (only_preview) {
message = create_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
std::move(message_content.first), i != 0, nullptr, false, DialogId());
MessageId new_message_id = message_send_options.schedule_date != 0
? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date)
: get_next_yet_unsent_message_id(d);
set_message_id(message, new_message_id);
m = message.get();
} else {
m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
dup_message_content(td_, dialog_id, message_content.first.get(),
MessageContentDupType::Send, MessageCopyOptions()),
&need_update_dialog_pos, i != 0);
}
auto ttl = message_content.second;
if (ttl > 0) {
m->ttl = ttl;
m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
}
m->media_album_id = media_album_id;
result.push_back(get_message_object(dialog_id, m, "send_message_group"));
if (!only_preview) {
save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
send_update_new_message(d, m);
}
}
if (need_update_dialog_pos) {
CHECK(!only_preview);
send_update_chat_last_message(d, "send_message_group");
}
return get_messages_object(-1, std::move(result), false);
}
void MessagesManager::save_send_message_log_event(DialogId dialog_id, const Message *m) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
auto log_event = SendMessageLogEvent(dialog_id, m);
CHECK(m->send_message_log_event_id == 0);
m->send_message_log_event_id =
binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendMessage, get_log_event_storer(log_event));
}
void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vector<int> bad_parts) {
bool is_edit = m->message_id.is_any_server();
LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << FullMessageId(dialog_id, m->message_id);
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
if (m->media_album_id != 0 && bad_parts.empty() && !is_secret && !is_edit) {
auto &request = pending_message_group_sends_[m->media_album_id];
request.dialog_id = dialog_id;
request.message_ids.push_back(m->message_id);
request.is_finished.push_back(false);
request.results.push_back(Status::OK());
}
auto content = is_edit ? m->edited_content.get() : m->content.get();
CHECK(content != nullptr);
auto content_type = content->get_type();
if (content_type == MessageContentType::Text) {
CHECK(!is_edit);
send_closure_later(actor_id(this), &MessagesManager::on_text_message_ready_to_send, dialog_id, m->message_id);
return;
}
FileId file_id = get_message_content_any_file_id(content); // any_file_id, because it could be a photo sent by ID
FileView file_view = td_->file_manager_->get_file_view(file_id);
FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content, td_);
LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id;
if (is_secret) {
CHECK(!is_edit);
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice(), layer);
if (secret_input_media.empty()) {
LOG(INFO) << "Ask to upload encrypted file " << file_id;
CHECK(file_view.is_encrypted_secret());
CHECK(file_id.is_valid());
bool is_inserted =
being_uploaded_files_
.emplace(file_id, std::make_pair(FullMessageId(dialog_id, m->message_id), thumbnail_file_id))
.second;
CHECK(is_inserted);
// need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get());
} else {
on_secret_message_media_uploaded(dialog_id, m, std::move(secret_input_media), file_id, thumbnail_file_id);
}
} else {
auto input_media =
get_input_media(content, td_, m->ttl, m->send_emoji, td_->auth_manager_->is_bot() && bad_parts.empty());
if (input_media == nullptr) {
if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll) {
return;
}
if (get_main_file_type(file_view.get_type()) == FileType::Photo) {
thumbnail_file_id = FileId();
}
LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts;
CHECK(file_id.is_valid());
bool is_inserted =
being_uploaded_files_
.emplace(file_id, std::make_pair(FullMessageId(dialog_id, m->message_id), thumbnail_file_id))
.second;
CHECK(is_inserted);
// need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get());
} else {
on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id);
}
}
}
void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Message *m,
tl_object_ptr<telegram_api::InputMedia> &&input_media, FileId file_id,
FileId thumbnail_file_id) {
if (G()->close_flag()) {
return;
}
CHECK(m != nullptr);
CHECK(input_media != nullptr);
auto message_id = m->message_id;
if (message_id.is_any_server()) {
const FormattedText *caption = get_message_content_caption(m->edited_content.get());
auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), m->edited_reply_markup);
bool was_uploaded = FileManager::extract_was_uploaded(input_media);
bool was_thumbnail_uploaded = FileManager::extract_was_thumbnail_uploaded(input_media);
LOG(INFO) << "Edit media from " << message_id << " in " << dialog_id;
auto schedule_date = get_message_schedule_date(m);
auto promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_id, message_id, file_id, thumbnail_file_id, schedule_date,
generation = m->edit_generation, was_uploaded, was_thumbnail_uploaded,
file_reference = FileManager::extract_file_reference(input_media)](Result<int32> result) mutable {
send_closure(actor_id, &MessagesManager::on_message_media_edited, dialog_id, message_id, file_id,
thumbnail_file_id, was_uploaded, was_thumbnail_uploaded, std::move(file_reference),
schedule_date, generation, std::move(result));
});
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(1 << 11, dialog_id, message_id, caption == nullptr ? "" : caption->text,
get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_message_media"),
std::move(input_media), std::move(input_reply_markup), schedule_date);
return;
}
if (m->media_album_id == 0) {
send_closure_later(
actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, message_id,
PromiseCreator::lambda([this, dialog_id, input_media = std::move(input_media), file_id,
thumbnail_file_id](Result<Message *> result) mutable {
if (result.is_error() || G()->close_flag()) {
return;
}
auto m = result.move_as_ok();
CHECK(m != nullptr);
CHECK(input_media != nullptr);
const FormattedText *caption = get_message_content_caption(m->content.get());
LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id << " in reply to "
<< m->reply_to_message_id;
int64 random_id = begin_send_message(dialog_id, m);
td_->create_handler<SendMediaQuery>()->send(
file_id, thumbnail_file_id, get_message_flags(m), dialog_id, get_send_message_as_input_peer(m),
m->reply_to_message_id, m->top_thread_message_id, get_message_schedule_date(m),
get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup),
get_input_message_entities(td_->contacts_manager_.get(), caption, "on_message_media_uploaded"),
caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), m->is_copy,
random_id, &m->send_query_ref);
}));
} else {
switch (input_media->get_id()) {
case telegram_api::inputMediaUploadedDocument::ID:
static_cast<telegram_api::inputMediaUploadedDocument *>(input_media.get())->flags_ |=
telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
// fallthrough
case telegram_api::inputMediaUploadedPhoto::ID:
case telegram_api::inputMediaDocumentExternal::ID:
case telegram_api::inputMediaPhotoExternal::ID:
LOG(INFO) << "Upload media from " << message_id << " in " << dialog_id;
td_->create_handler<UploadMediaQuery>()->send(dialog_id, message_id, file_id, thumbnail_file_id,
std::move(input_media));
break;
case telegram_api::inputMediaDocument::ID:
case telegram_api::inputMediaPhoto::ID:
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
dialog_id, message_id, Status::OK());
break;
default:
LOG(ERROR) << "Have wrong input media " << to_string(input_media);
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
dialog_id, message_id, Status::Error(400, "Invalid input media"));
}
}
}
void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, const Message *m,
SecretInputMedia &&secret_input_media, FileId file_id,
FileId thumbnail_file_id) {
if (G()->close_flag()) {
return;
}
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
if (secret_input_media.empty()) {
// the media can't be sent to the chat
LOG(INFO) << "Can't send a media message to " << dialog_id;
fail_send_message({dialog_id, m->message_id}, Status::Error(400, "The file can't be sent to the secret chat"));
return;
}
/*
if (m->media_album_id != 0) {
switch (secret_input_media->input_file_->get_id()) {
case telegram_api::inputEncryptedFileUploaded::ID:
case telegram_api::inputEncryptedFileBigUploaded::ID:
LOG(INFO) << "Upload media from " << m->message_id << " in " << dialog_id;
return td_->create_handler<UploadEncryptedMediaQuery>()->send(dialog_id, m->message_id, std::move(secret_input_media));
case telegram_api::inputEncryptedFile::ID:
return send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
dialog_id, m->message_id, Status::OK());
default:
LOG(ERROR) << "Have wrong secret input media " << to_string(secret_input_media->input_file_);
return send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
dialog_id, m->message_id, Status::Error(400, "Invalid input media"));
}
*/
// TODO use file_id, thumbnail_file_id, was_uploaded, was_thumbnail_uploaded,
// invalidate partial remote location for file_id in case of failed upload even message has already been deleted
send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, m->message_id,
PromiseCreator::lambda([this, dialog_id, secret_input_media = std::move(secret_input_media)](
Result<Message *> result) mutable {
if (result.is_error() || G()->close_flag()) {
return;
}
auto m = result.move_as_ok();
CHECK(m != nullptr);
CHECK(!secret_input_media.empty());
send_secret_message(dialog_id, m, std::move(secret_input_media));
}));
}
void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, SecretInputMedia media) {
CHECK(dialog_id.get_type() == DialogType::SecretChat);
int64 random_id = begin_send_message(dialog_id, m);
auto text = get_message_content_text(m->content.get());
vector<tl_object_ptr<secret_api::MessageEntity>> entities;
if (text != nullptr && !text->entities.empty()) {
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
entities = get_input_secret_message_entities(text->entities, layer);
}
int32 flags = 0;
if (m->reply_to_random_id != 0) {
flags |= secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK;
}
if (m->via_bot_user_id.is_valid()) {
flags |= secret_api::decryptedMessage::VIA_BOT_NAME_MASK;
}
if (!media.empty()) {
flags |= secret_api::decryptedMessage::MEDIA_MASK;
}
if (!entities.empty()) {
flags |= secret_api::decryptedMessage::ENTITIES_MASK;
}
if (m->media_album_id != 0) {
CHECK(m->media_album_id < 0);
flags |= secret_api::decryptedMessage::GROUPED_ID_MASK;
}
if (m->disable_notification) {
flags |= secret_api::decryptedMessage::SILENT_MASK;
}
send_closure(
td_->secret_chats_manager_, &SecretChatsManager::send_message, dialog_id.get_secret_chat_id(),
make_tl_object<secret_api::decryptedMessage>(
flags, false /*ignored*/, random_id, m->ttl,
m->content->get_type() == MessageContentType::Text ? text->text : string(), std::move(media.decrypted_media_),
std::move(entities), td_->contacts_manager_->get_user_first_username(m->via_bot_user_id),
m->reply_to_random_id, -m->media_album_id),
std::move(media.input_file_), Promise<Unit>());
}
void MessagesManager::on_upload_message_media_success(DialogId dialog_id, MessageId message_id,
tl_object_ptr<telegram_api::MessageMedia> &&media) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
CHECK(message_id.is_yet_unsent());
Message *m = get_message(d, message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return; // the message should be deleted soon
}
auto caption = get_message_content_caption(m->content.get());
auto content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), dialog_id,
false, UserId(), nullptr, nullptr, "on_upload_message_media_success");
bool is_content_changed = false;
bool need_update = update_message_content(dialog_id, m, std::move(content), true, true, is_content_changed);
if (need_update) {
send_update_message_content(d, m, true, "on_upload_message_media_success");
}
if (is_content_changed || need_update) {
on_message_changed(d, m, need_update, "on_upload_message_media_success");
}
auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true);
Status result;
if (input_media == nullptr) {
result = Status::Error(400, "Failed to upload file");
}
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
m->message_id, std::move(result));
}
void MessagesManager::on_upload_message_media_file_part_missing(DialogId dialog_id, MessageId message_id,
int bad_part) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
Message *m = get_message(d, message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(get_dialog(dialog_id), 5);
return; // the message should be deleted soon
}
CHECK(dialog_id.get_type() != DialogType::SecretChat);
do_send_message(dialog_id, m, {bad_part});
}
void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
Message *m = get_message(d, message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(get_dialog(dialog_id), 5);
return; // the message should be deleted soon
}
CHECK(dialog_id.get_type() != DialogType::SecretChat);
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
m->message_id, std::move(error));
}
void MessagesManager::on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id,
Status result) {
CHECK(media_album_id < 0);
auto it = pending_message_group_sends_.find(media_album_id);
if (it == pending_message_group_sends_.end()) {
// the group may be already sent or failed to be sent
return;
}
auto &request = it->second;
CHECK(request.dialog_id == dialog_id);
auto message_it = std::find(request.message_ids.begin(), request.message_ids.end(), message_id);
if (message_it == request.message_ids.end()) {
// the message may be already deleted and the album is recreated without it
CHECK(message_id.is_yet_unsent());
LOG_CHECK(get_message({dialog_id, message_id}) == nullptr)
<< dialog_id << ' ' << request.message_ids << ' ' << message_id << ' ' << request.finished_count << ' '
<< request.is_finished << ' ' << request.results;
return;
}
auto pos = static_cast<size_t>(message_it - request.message_ids.begin());
if (request.is_finished[pos]) {
LOG(INFO) << "Upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id
<< " at pos " << pos << " was already finished";
return;
}
LOG(INFO) << "Finish to upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id
<< " at pos " << pos << " with result " << result
<< " and previous finished_count = " << request.finished_count;
request.results[pos] = std::move(result);
request.is_finished[pos] = true;
request.finished_count++;
if (request.finished_count == request.message_ids.size() || request.results[pos].is_error()) {
// must use send_closure_later if some messages may be being deleted now
// but this function is called only through send_closure_later, so there should be no being deleted messages
// we must use synchronous calls to keep the correct message order during copying of multiple messages
// but "request" iterator can be invalidated by do_send_message_group, so it must not be used below
auto message_ids = request.message_ids;
for (auto request_message_id : message_ids) {
LOG(INFO) << "Send on_media_message_ready_to_send for " << request_message_id << " in " << dialog_id;
auto promise = PromiseCreator::lambda([this, media_album_id](Result<Message *> result) {
if (result.is_error() || G()->close_flag()) {
return;
}
auto m = result.move_as_ok();
CHECK(m != nullptr);
CHECK(m->media_album_id == media_album_id);
do_send_message_group(media_album_id);
// send_closure_later(actor_id, &MessagesManager::do_send_message_group, media_album_id);
});
// send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id,
// request_message_id, std::move(promise));
on_media_message_ready_to_send(dialog_id, request_message_id, std::move(promise));
}
}
}
void MessagesManager::do_send_message_group(int64 media_album_id) {
if (G()->close_flag()) {
return;
}
CHECK(media_album_id < 0);
auto it = pending_message_group_sends_.find(media_album_id);
if (it == pending_message_group_sends_.end()) {
// the group may be already sent or failed to be sent
return;
}
auto &request = it->second;
auto dialog_id = request.dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto default_status = can_send_message(dialog_id);
bool success = default_status.is_ok();
vector<FileId> file_ids;
vector<int64> random_ids;
vector<tl_object_ptr<telegram_api::inputSingleMedia>> input_single_media;
tl_object_ptr<telegram_api::InputPeer> as_input_peer;
MessageId reply_to_message_id;
MessageId top_thread_message_id;
int32 flags = 0;
int32 schedule_date = 0;
bool is_copy = false;
for (size_t i = 0; i < request.message_ids.size(); i++) {
auto *m = get_message(d, request.message_ids[i]);
if (m == nullptr) {
// skip deleted messages
random_ids.push_back(0);
continue;
}
reply_to_message_id = m->reply_to_message_id;
top_thread_message_id = m->top_thread_message_id;
flags = get_message_flags(m);
schedule_date = get_message_schedule_date(m);
is_copy = m->is_copy;
as_input_peer = get_send_message_as_input_peer(m);
file_ids.push_back(get_message_content_any_file_id(m->content.get()));
random_ids.push_back(begin_send_message(dialog_id, m));
LOG(INFO) << "Have file " << file_ids.back() << " in " << m->message_id << " with result " << request.results[i]
<< " and is_finished = " << static_cast<bool>(request.is_finished[i]);
if (request.results[i].is_error() || !request.is_finished[i]) {
success = false;
continue;
}
const FormattedText *caption = get_message_content_caption(m->content.get());
auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true);
if (input_media == nullptr) {
// TODO return CHECK
auto file_id = get_message_content_any_file_id(m->content.get());
auto file_view = td_->file_manager_->get_file_view(file_id);
bool has_remote = file_view.has_remote_location();
bool is_web = has_remote ? file_view.remote_location().is_web() : false;
LOG(FATAL) << request.dialog_id << " " << request.finished_count << " " << i << " " << request.message_ids << " "
<< request.is_finished << " " << request.results << " " << m->ttl << " " << has_remote << " "
<< 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, false, -1));
}
auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group");
int32 input_single_media_flags = 0;
if (!entities.empty()) {
input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK;
}
input_single_media.push_back(make_tl_object<telegram_api::inputSingleMedia>(
input_single_media_flags, std::move(input_media), random_ids.back(), caption == nullptr ? "" : caption->text,
std::move(entities)));
}
if (!success) {
if (default_status.is_ok()) {
default_status = Status::Error(400, "Group send failed");
}
for (size_t i = 0; i < random_ids.size(); i++) {
if (random_ids[i] != 0) {
on_send_message_fail(random_ids[i],
request.results[i].is_error() ? std::move(request.results[i]) : default_status.clone());
}
}
pending_message_group_sends_.erase(it);
return;
}
LOG_CHECK(request.finished_count == request.message_ids.size())
<< request.finished_count << " " << request.message_ids.size();
pending_message_group_sends_.erase(it);
LOG(INFO) << "Begin to send media group " << media_album_id << " to " << dialog_id;
if (input_single_media.empty()) {
LOG(INFO) << "Media group " << media_album_id << " from " << dialog_id << " is empty";
}
td_->create_handler<SendMultiMediaQuery>()->send(flags, dialog_id, std::move(as_input_peer), reply_to_message_id,
top_thread_message_id, schedule_date, std::move(file_ids),
std::move(input_single_media), is_copy);
}
void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageId message_id) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id;
auto m = get_message({dialog_id, message_id});
if (m == nullptr) {
return;
}
CHECK(message_id.is_yet_unsent());
auto content = m->content.get();
CHECK(content != nullptr);
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(!message_id.is_scheduled());
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice(), layer));
} else {
const FormattedText *message_text = get_message_content_text(content);
CHECK(message_text != nullptr);
int64 random_id = begin_send_message(dialog_id, m);
td_->create_handler<SendMessageQuery>()->send(
get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), m->reply_to_message_id,
m->top_thread_message_id, get_message_schedule_date(m),
get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup),
get_input_message_entities(td_->contacts_manager_.get(), message_text->entities, "do_send_message"),
message_text->text, m->is_copy, random_id, &m->send_query_ref);
}
}
void MessagesManager::on_media_message_ready_to_send(DialogId dialog_id, MessageId message_id,
Promise<Message *> &&promise) {
LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id;
CHECK(promise);
if (!G()->parameters().use_file_db || message_id.is_scheduled()) { // ResourceManager::Mode::Greedy
auto m = get_message({dialog_id, message_id});
if (m != nullptr) {
promise.set_value(std::move(m));
}
return;
}
auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
auto &queue = yet_unsent_media_queues_[queue_id];
auto it = queue.find(message_id);
if (it == queue.end()) {
if (queue.empty()) {
yet_unsent_media_queues_.erase(queue_id);
}
LOG(INFO) << "Can't find " << message_id << " in the queue of " << dialog_id;
auto m = get_message({dialog_id, message_id});
if (m != nullptr) {
promise.set_value(std::move(m));
}
return;
}
if (it->second) {
promise.set_error(Status::Error(500, "Duplicate promise"));
return;
}
it->second = std::move(promise);
on_yet_unsent_media_queue_updated(dialog_id);
}
void MessagesManager::on_yet_unsent_media_queue_updated(DialogId dialog_id) {
auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
while (true) {
auto it = yet_unsent_media_queues_.find(queue_id);
if (it == yet_unsent_media_queues_.end()) {
return;
}
auto &queue = it->second;
if (queue.empty()) {
yet_unsent_media_queues_.erase(it);
return;
}
auto first_it = queue.begin();
if (!first_it->second) {
return;
}
auto m = get_message({dialog_id, first_it->first});
auto promise = std::move(first_it->second);
queue.erase(first_it);
LOG(INFO) << "Queue for " << dialog_id << " now has size " << queue.size();
// don't use it/queue/first_it after promise is called
if (m != nullptr) {
LOG(INFO) << "Can send " << FullMessageId{dialog_id, m->message_id};
promise.set_value(std::move(m));
} else {
promise.set_error(Status::Error(400, "Message not found"));
}
}
}
Result<MessageId> MessagesManager::send_bot_start_message(UserId bot_user_id, DialogId dialog_id,
const string &parameter) {
LOG(INFO) << "Begin to send bot start message to " << dialog_id;
CHECK(!td_->auth_manager_->is_bot());
TRY_RESULT(bot_data, td_->contacts_manager_->get_bot_data(bot_user_id));
Dialog *d = get_dialog_force(dialog_id, "send_bot_start_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
bool is_chat_with_bot = false;
switch (dialog_id.get_type()) {
case DialogType::User:
if (dialog_id.get_user_id() != bot_user_id) {
return Status::Error(400, "Can't send start message to a private chat other than chat with the bot");
}
is_chat_with_bot = true;
break;
case DialogType::Chat: {
if (!bot_data.can_join_groups) {
return Status::Error(400, "Bot can't join groups");
}
auto chat_id = dialog_id.get_chat_id();
if (!td_->contacts_manager_->have_input_peer_chat(chat_id, AccessRights::Write)) {
return Status::Error(400, "Can't access the chat");
}
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_invite_users()) {
return Status::Error(400, "Need administrator rights to invite a bot to the group chat");
}
break;
}
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Write)) {
return Status::Error(400, "Can't access the chat");
}
switch (td_->contacts_manager_->get_channel_type(channel_id)) {
case ChannelType::Megagroup:
if (!bot_data.can_join_groups) {
return Status::Error(400, "The bot can't join groups");
}
break;
case ChannelType::Broadcast:
return Status::Error(400, "Bots can't be invited to channel chats. Add them as administrators instead");
case ChannelType::Unknown:
default:
UNREACHABLE();
}
auto status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (!status.can_invite_users()) {
return Status::Error(400, "Need administrator rights to invite a bot to the supergroup chat");
}
break;
}
case DialogType::SecretChat:
return Status::Error(400, "Can't send bot start message to a secret chat");
case DialogType::None:
default:
UNREACHABLE();
}
string text = "/start";
if (!is_chat_with_bot) {
text += '@';
text += bot_data.username;
}
vector<MessageEntity> text_entities;
text_entities.emplace_back(MessageEntity::Type::BotCommand, 0, narrow_cast<int32>(text.size()));
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
create_text_message_content(text, std::move(text_entities), WebPageId()),
&need_update_dialog_pos);
m->is_bot_start_message = true;
send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_bot_start_message");
}
if (parameter.empty() && is_chat_with_bot) {
save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
} else {
save_send_bot_start_message_log_event(bot_user_id, dialog_id, parameter, m);
send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id,
m->message_id, parameter);
}
return m->message_id;
}
class MessagesManager::SendBotStartMessageLogEvent {
public:
UserId bot_user_id;
DialogId dialog_id;
string parameter;
const Message *m_in = nullptr;
unique_ptr<Message> m_out;
template <class StorerT>
void store(StorerT &storer) const {
td::store(bot_user_id, storer);
td::store(dialog_id, storer);
td::store(parameter, storer);
td::store(*m_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(bot_user_id, parser);
td::parse(dialog_id, parser);
td::parse(parameter, parser);
td::parse(m_out, parser);
}
};
void MessagesManager::save_send_bot_start_message_log_event(UserId bot_user_id, DialogId dialog_id,
const string &parameter, const Message *m) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
SendBotStartMessageLogEvent log_event;
log_event.bot_user_id = bot_user_id;
log_event.dialog_id = dialog_id;
log_event.parameter = parameter;
log_event.m_in = m;
CHECK(m->send_message_log_event_id == 0);
m->send_message_log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendBotStartMessage,
get_log_event_storer(log_event));
}
void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, MessageId message_id,
const string &parameter) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Do send bot start " << FullMessageId(dialog_id, message_id) << " to bot " << bot_user_id;
auto m = get_message({dialog_id, message_id});
if (m == nullptr) {
return;
}
int64 random_id = begin_send_message(dialog_id, m);
telegram_api::object_ptr<telegram_api::InputPeer> input_peer = dialog_id.get_type() == DialogType::User
? make_tl_object<telegram_api::inputPeerEmpty>()
: get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
return on_send_message_fail(random_id, Status::Error(400, "Chat is not accessible"));
}
auto r_bot_input_user = td_->contacts_manager_->get_input_user(bot_user_id);
if (r_bot_input_user.is_error()) {
return on_send_message_fail(random_id, r_bot_input_user.move_as_error());
}
m->send_query_ref = td_->create_handler<StartBotQuery>()->send(r_bot_input_user.move_as_ok(), dialog_id,
std::move(input_peer), parameter, random_id);
}
Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dialog_id, MessageId top_thread_message_id,
MessageId reply_to_message_id,
tl_object_ptr<td_api::messageSendOptions> &&options,
int64 query_id, const string &result_id,
bool hide_via_bot) {
LOG(INFO) << "Begin to send inline query result message to " << dialog_id << " in reply to " << reply_to_message_id;
Dialog *d = get_dialog_force(dialog_id, "send_inline_query_result_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false));
bool to_secret = false;
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
// ok
break;
case DialogType::Channel: {
auto channel_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!channel_status.can_use_inline_bots()) {
return Status::Error(400, "Can't use inline bots in the chat");
}
break;
}
case DialogType::SecretChat:
to_secret = true;
// ok
break;
case DialogType::None:
default:
UNREACHABLE();
}
const InlineMessageContent *content = td_->inline_queries_manager_->get_inline_message_content(query_id, result_id);
if (content == nullptr) {
return Status::Error(400, "Inline query result not found");
}
reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
TRY_STATUS(can_use_message_send_options(message_send_options, content->message_content, 0));
TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, td_));
TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
dup_message_content(td_, dialog_id, content->message_content.get(),
MessageContentDupType::SendViaBot, MessageCopyOptions()),
&need_update_dialog_pos, false, nullptr, true);
m->hide_via_bot = hide_via_bot;
if (!hide_via_bot) {
m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id);
}
if (content->message_reply_markup != nullptr && !to_secret) {
m->reply_markup = make_unique<ReplyMarkup>(*content->message_reply_markup);
}
m->disable_web_page_preview = content->disable_web_page_preview;
m->clear_draft = !hide_via_bot;
if (m->clear_draft) {
if (top_thread_message_id.is_valid()) {
set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore();
} else {
update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
}
}
send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_inline_query_result_message");
}
if (to_secret) {
save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
return m->message_id;
}
save_send_inline_query_result_message_log_event(dialog_id, m, query_id, result_id);
send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, m->message_id,
query_id, result_id);
return m->message_id;
}
class MessagesManager::SendInlineQueryResultMessageLogEvent {
public:
DialogId dialog_id;
int64 query_id;
string result_id;
const Message *m_in = nullptr;
unique_ptr<Message> m_out;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id, storer);
td::store(query_id, storer);
td::store(result_id, storer);
td::store(*m_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id, parser);
td::parse(query_id, parser);
td::parse(result_id, parser);
td::parse(m_out, parser);
}
};
void MessagesManager::save_send_inline_query_result_message_log_event(DialogId dialog_id, const Message *m,
int64 query_id, const string &result_id) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
SendInlineQueryResultMessageLogEvent log_event;
log_event.dialog_id = dialog_id;
log_event.query_id = query_id;
log_event.result_id = result_id;
log_event.m_in = m;
CHECK(m->send_message_log_event_id == 0);
m->send_message_log_event_id = binlog_add(
G()->td_db()->get_binlog(), LogEvent::HandlerType::SendInlineQueryResultMessage, get_log_event_storer(log_event));
}
void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, MessageId message_id, int64 query_id,
const string &result_id) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Do send inline query result " << FullMessageId(dialog_id, message_id);
auto m = get_message({dialog_id, message_id});
if (m == nullptr) {
return;
}
int64 random_id = begin_send_message(dialog_id, m);
auto flags = get_message_flags(m);
if (!m->via_bot_user_id.is_valid() || m->hide_via_bot) {
flags |= telegram_api::messages_sendInlineBotResult::HIDE_VIA_MASK;
}
m->send_query_ref = td_->create_handler<SendInlineBotResultQuery>()->send(
flags, dialog_id, get_send_message_as_input_peer(m), m->reply_to_message_id, m->top_thread_message_id,
get_message_schedule_date(m), random_id, query_id, result_id);
}
bool MessagesManager::has_qts_messages(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
return td_->option_manager_->get_option_integer("session_count") > 1;
case DialogType::Channel:
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
}
bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, bool is_editing,
bool only_reply_markup) const {
if (m == nullptr) {
return false;
}
if (m->message_id.is_yet_unsent()) {
return false;
}
if (m->message_id.is_local()) {
return false;
}
if (m->forward_info != nullptr || m->had_forward_info) {
return false;
}
if (m->had_reply_markup) {
return false;
}
if (m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
return false;
}
auto my_id = td_->contacts_manager_->get_my_id();
if (m->via_bot_user_id.is_valid() && (m->via_bot_user_id != my_id || m->message_id.is_scheduled())) {
return false;
}
bool is_bot = td_->auth_manager_->is_bot();
auto content_type = m->content->get_type();
DialogId my_dialog_id(my_id);
bool has_edit_time_limit = !(is_bot && m->is_outgoing) && dialog_id != my_dialog_id &&
content_type != MessageContentType::Poll &&
content_type != MessageContentType::LiveLocation && !m->message_id.is_scheduled();
switch (dialog_id.get_type()) {
case DialogType::User:
if (!m->is_outgoing && dialog_id != my_dialog_id && !m->via_bot_user_id.is_valid()) {
return false;
}
break;
case DialogType::Chat:
if (!m->is_outgoing && !m->via_bot_user_id.is_valid()) {
return false;
}
break;
case DialogType::Channel: {
if (m->via_bot_user_id.is_valid()) {
// outgoing via_bot messages can always be edited
break;
}
auto channel_id = dialog_id.get_channel_id();
auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (m->is_channel_post) {
if (m->message_id.is_scheduled()) {
if (!channel_status.can_post_messages()) {
return false;
}
} else {
if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) {
return false;
}
if (channel_status.can_edit_messages()) {
has_edit_time_limit = false;
}
}
if (is_bot && only_reply_markup) {
has_edit_time_limit = false;
}
} else {
if (!m->is_outgoing) {
return false;
}
if (channel_status.can_pin_messages()) {
has_edit_time_limit = false;
}
}
break;
}
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
if (has_edit_time_limit) {
const int32 DEFAULT_EDIT_TIME_LIMIT = 2 * 86400;
int64 edit_time_limit = td_->option_manager_->get_option_integer("edit_time_limit", DEFAULT_EDIT_TIME_LIMIT);
if (G()->unix_time_cached() - m->date - (is_editing ? 300 : 0) >= edit_time_limit) {
return false;
}
}
switch (content_type) {
case MessageContentType::Animation:
case MessageContentType::Audio:
case MessageContentType::Document:
case MessageContentType::Game:
case MessageContentType::Photo:
case MessageContentType::Text:
case MessageContentType::Video:
case MessageContentType::VoiceNote:
return true;
case MessageContentType::LiveLocation: {
if (is_bot && only_reply_markup) {
// there is no caption to edit, but bot can edit inline reply_markup
return true;
}
return G()->unix_time_cached() - m->date < get_message_content_live_location_period(m->content.get());
}
case MessageContentType::Poll: {
if (is_bot && only_reply_markup) {
// there is no caption to edit, but bot can edit inline reply_markup
return true;
}
if (m->message_id.is_scheduled()) {
return false;
}
return !get_message_content_poll_is_closed(td_, m->content.get());
}
case MessageContentType::Contact:
case MessageContentType::Dice:
case MessageContentType::Location:
case MessageContentType::Sticker:
case MessageContentType::Venue:
case MessageContentType::VideoNote:
// there is no caption to edit, but bot can edit inline reply_markup
return is_bot && only_reply_markup;
case MessageContentType::Invoice:
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
case MessageContentType::ChatChangePhoto:
case MessageContentType::ChatDeletePhoto:
case MessageContentType::ChatDeleteHistory:
case MessageContentType::ChatAddUsers:
case MessageContentType::ChatJoinedByLink:
case MessageContentType::ChatDeleteUser:
case MessageContentType::ChatMigrateTo:
case MessageContentType::ChannelCreate:
case MessageContentType::ChannelMigrateFrom:
case MessageContentType::PinMessage:
case MessageContentType::GameScore:
case MessageContentType::ScreenshotTaken:
case MessageContentType::ChatSetTtl:
case MessageContentType::Call:
case MessageContentType::PaymentSuccessful:
case MessageContentType::ContactRegistered:
case MessageContentType::ExpiredPhoto:
case MessageContentType::ExpiredVideo:
case MessageContentType::CustomServiceAction:
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::ProximityAlertTriggered:
case MessageContentType::GroupCall:
case MessageContentType::InviteToGroupCall:
case MessageContentType::ChatSetTheme:
case MessageContentType::WebViewDataSent:
case MessageContentType::WebViewDataReceived:
case MessageContentType::GiftPremium:
case MessageContentType::TopicCreate:
case MessageContentType::TopicEdit:
case MessageContentType::SuggestProfilePhoto:
case MessageContentType::WriteAccessAllowed:
return false;
default:
UNREACHABLE();
}
return false;
}
bool MessagesManager::can_resend_message(const Message *m) const {
if (m->send_error_code != 429 && m->send_error_message != "Message is too old to be re-sent automatically" &&
m->send_error_message != "SCHEDULE_TOO_MUCH" && m->send_error_message != "SEND_AS_PEER_INVALID") {
return false;
}
if (m->is_bot_start_message) {
return false;
}
if (m->forward_info != nullptr || m->real_forward_from_dialog_id.is_valid()) {
// TODO implement resending of forwarded messages
return false;
}
auto content_type = m->content->get_type();
if (m->via_bot_user_id.is_valid() || m->hide_via_bot) {
// via bot message
if (!can_have_input_media(td_, m->content.get(), false)) {
return false;
}
// resend via_bot message as an ordinary message if error code is 429
// TODO support other error codes
}
if (content_type == MessageContentType::ChatSetTtl || content_type == MessageContentType::ScreenshotTaken) {
// TODO implement resending of ChatSetTtl and ScreenshotTaken messages
return false;
}
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_->is_megagroup_channel(dialog_id.get_channel_id());
default:
return false;
}
}
bool MessagesManager::is_forum_channel(DialogId dialog_id) const {
return dialog_id.get_type() == DialogType::Channel &&
td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id());
}
bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const {
if (dialog_id.get_type() != DialogType::Channel) {
return false;
}
return td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id());
}
bool MessagesManager::is_deleted_secret_chat(DialogId dialog_id) const {
return is_deleted_secret_chat(get_dialog(dialog_id));
}
bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const {
if (d == nullptr) {
return true;
}
if (d->dialog_id.get_type() != DialogType::SecretChat) {
return false;
}
if (d->order != DEFAULT_ORDER || d->messages != nullptr) {
return false;
}
auto state = td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id());
if (state != SecretChatState::Closed) {
return false;
}
return true;
}
int32 MessagesManager::get_message_schedule_date(const Message *m) {
CHECK(m != nullptr);
if (!m->message_id.is_scheduled()) {
return 0;
}
if (m->edited_schedule_date != 0) {
return m->edited_schedule_date;
}
return m->date;
}
DialogId MessagesManager::get_message_original_sender(const Message *m) {
CHECK(m != nullptr);
if (m->forward_info != nullptr) {
auto forward_info = m->forward_info.get();
if (forward_info->is_imported || is_forward_info_sender_hidden(forward_info)) {
return DialogId();
}
if (forward_info->message_id.is_valid() || forward_info->sender_dialog_id.is_valid()) {
return forward_info->sender_dialog_id;
}
return DialogId(forward_info->sender_user_id);
}
return get_message_sender(m);
}
DialogId MessagesManager::get_message_sender(const Message *m) {
CHECK(m != nullptr);
return m->sender_dialog_id.is_valid() ? m->sender_dialog_id : DialogId(m->sender_user_id);
}
void MessagesManager::edit_message_text(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
if (input_message_content == nullptr) {
return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageText::ID) {
return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText"));
}
LOG(INFO) << "Begin to edit text of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_text");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_text");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!can_edit_message(dialog_id, m, true)) {
return promise.set_error(Status::Error(400, "Message can't be edited"));
}
MessageContentType old_message_content_type = m->content->get_type();
if (old_message_content_type != MessageContentType::Text && old_message_content_type != MessageContentType::Game) {
return promise.set_error(Status::Error(400, "There is no text in the message to edit"));
}
auto r_input_message_text =
process_input_message_text(td_, dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot());
if (r_input_message_text.is_error()) {
return promise.set_error(r_input_message_text.move_as_error());
}
InputMessageText input_message_text = r_input_message_text.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
int32 flags = 0;
if (input_message_text.disable_web_page_preview) {
flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(flags, dialog_id, m->message_id, input_message_text.text.text,
get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities,
"edit_message_text"),
nullptr, std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_message_live_location(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::location> &&input_location, int32 heading,
int32 proximity_alert_radius, Promise<Unit> &&promise) {
LOG(INFO) << "Begin to edit live location of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_live_location");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_live_location");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!can_edit_message(dialog_id, m, true)) {
return promise.set_error(Status::Error(400, "Message can't be edited"));
}
MessageContentType old_message_content_type = m->content->get_type();
if (old_message_content_type != MessageContentType::LiveLocation) {
return promise.set_error(Status::Error(400, "There is no live location in the message to edit"));
}
if (m->message_id.is_scheduled()) {
LOG(ERROR) << "Have " << full_message_id << " with live location";
return promise.set_error(Status::Error(400, "Can't edit live location in scheduled message"));
}
Location location(input_location);
if (location.empty() && input_location != nullptr) {
return promise.set_error(Status::Error(400, "Invalid location specified"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
int32 flags = 0;
if (location.empty()) {
flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK;
}
if (heading != 0) {
flags |= telegram_api::inputMediaGeoLive::HEADING_MASK;
}
flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK;
auto input_media = telegram_api::make_object<telegram_api::inputMediaGeoLive>(
flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius);
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(),
std::move(input_media), std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message) {
if (m->edited_content == nullptr) {
return;
}
cancel_upload_message_content_files(m->edited_content.get());
m->edited_content = nullptr;
m->edited_reply_markup = nullptr;
m->edit_generation = 0;
m->edit_promise.set_error(Status::Error(400, error_message));
}
void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id,
FileId thumbnail_file_id, bool was_uploaded, bool was_thumbnail_uploaded,
string file_reference, int32 schedule_date, uint64 generation,
Result<int32> &&result) {
// must not run getDifference
CHECK(message_id.is_any_server());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto m = get_message(d, message_id);
if (m == nullptr || m->edit_generation != generation) {
// message is already deleted or was edited again
return;
}
CHECK(m->edited_content != nullptr);
if (result.is_ok()) {
// message content has already been replaced from updateEdit{Channel,}Message
// need only merge files from edited_content with their uploaded counterparts
// updateMessageContent was already sent and needs to be sent again,
// only if 'i' and 't' sizes from edited_content was added to the photo
auto pts = result.ok();
LOG(INFO) << "Successfully edited " << message_id << " in " << dialog_id << " with pts = " << pts
<< " and last edit pts = " << m->last_edit_pts;
std::swap(m->content, m->edited_content);
bool need_send_update_message_content = m->edited_content->get_type() == MessageContentType::Photo &&
m->content->get_type() == MessageContentType::Photo;
bool need_merge_files = pts != 0 && pts == m->last_edit_pts;
bool is_content_changed = false;
bool need_update =
update_message_content(dialog_id, m, std::move(m->edited_content), need_merge_files, true, is_content_changed);
if (need_send_update_message_content) {
if (need_update) {
send_update_message_content(d, m, true, "on_message_media_edited");
}
if (is_content_changed || need_update) {
on_message_changed(d, m, need_update, "on_message_media_edited");
}
}
} else {
LOG(INFO) << "Failed to edit " << message_id << " in " << dialog_id << ": " << result.error();
if (was_uploaded) {
if (was_thumbnail_uploaded) {
CHECK(thumbnail_file_id.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
td_->file_manager_->delete_partial_remote_location(thumbnail_file_id);
}
CHECK(file_id.is_valid());
auto error_message = result.error().message();
if (begins_with(error_message, "FILE_PART_") && ends_with(error_message, "_MISSING")) {
do_send_message(dialog_id, m, {to_integer<int32>(error_message.substr(10))});
return;
}
if (result.error().code() != 429 && result.error().code() < 500 && !G()->close_flag()) {
td_->file_manager_->delete_partial_remote_location(file_id);
}
} else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(result.error())) {
if (file_id.is_valid()) {
VLOG(file_references) << "Receive " << result.error() << " for " << file_id;
td_->file_manager_->delete_file_reference(file_id, file_reference);
do_send_message(dialog_id, m, {-1});
return;
} else {
LOG(ERROR) << "Receive file reference error, but have no file_id";
}
}
cancel_upload_message_content_files(m->edited_content.get());
if (dialog_id.get_type() != DialogType::SecretChat) {
get_message_from_server({dialog_id, m->message_id}, Auto(), "on_message_media_edited");
}
}
if (m->edited_schedule_date == schedule_date) {
m->edited_schedule_date = 0;
}
m->edited_content = nullptr;
m->edited_reply_markup = nullptr;
m->edit_generation = 0;
if (result.is_ok()) {
m->edit_promise.set_value(Unit());
} else {
m->edit_promise.set_error(result.move_as_error());
}
}
void MessagesManager::edit_message_media(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
if (input_message_content == nullptr) {
return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageAnimation::ID &&
new_message_content_type != td_api::inputMessageAudio::ID &&
new_message_content_type != td_api::inputMessageDocument::ID &&
new_message_content_type != td_api::inputMessagePhoto::ID &&
new_message_content_type != td_api::inputMessageVideo::ID) {
return promise.set_error(Status::Error(400, "Unsupported input message content type"));
}
LOG(INFO) << "Begin to edit media of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_media");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_media");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!can_edit_message(dialog_id, m, true)) {
return promise.set_error(Status::Error(400, "Message can't be edited"));
}
CHECK(m->message_id.is_any_server());
MessageContentType old_message_content_type = m->content->get_type();
if (old_message_content_type != MessageContentType::Animation &&
old_message_content_type != MessageContentType::Audio &&
old_message_content_type != MessageContentType::Document &&
old_message_content_type != MessageContentType::Photo && old_message_content_type != MessageContentType::Video) {
return promise.set_error(Status::Error(400, "There is no media in the message to edit"));
}
if (m->ttl > 0) {
return promise.set_error(Status::Error(400, "Can't edit media in self-destructing message"));
}
auto r_input_message_content = process_input_message_content(dialog_id, std::move(input_message_content));
if (r_input_message_content.is_error()) {
return promise.set_error(r_input_message_content.move_as_error());
}
InputMessageContent content = r_input_message_content.move_as_ok();
if (content.ttl > 0) {
return promise.set_error(Status::Error(400, "Can't enable self-destruction for media"));
}
if (m->media_album_id != 0) {
auto new_content_type = content.content->get_type();
if (old_message_content_type != new_content_type) {
if (!is_allowed_media_group_content(new_content_type)) {
return promise.set_error(Status::Error(400, "Message content type can't be used in an album"));
}
if (is_homogenous_media_group_content(old_message_content_type) ||
is_homogenous_media_group_content(new_content_type)) {
return promise.set_error(Status::Error(400, "Can't change media type in the album"));
}
}
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
cancel_edit_message_media(dialog_id, m, "Canceled by new editMessageMedia request");
m->edited_content =
dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions());
CHECK(m->edited_content != nullptr);
m->edited_reply_markup = r_new_reply_markup.move_as_ok();
m->edit_generation = ++current_message_edit_generation_;
m->edit_promise = std::move(promise);
do_send_message(dialog_id, m);
}
void MessagesManager::edit_message_caption(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::formattedText> &&input_caption,
Promise<Unit> &&promise) {
LOG(INFO) << "Begin to edit caption of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_caption");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_caption");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!can_edit_message(dialog_id, m, true)) {
return promise.set_error(Status::Error(400, "Message can't be edited"));
}
if (!can_have_message_content_caption(m->content->get_type())) {
return promise.set_error(Status::Error(400, "There is no caption in the message to edit"));
}
auto r_caption =
get_formatted_text(td_, dialog_id, std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false);
if (r_caption.is_error()) {
return promise.set_error(r_caption.move_as_error());
}
auto caption = r_caption.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(1 << 11, dialog_id, m->message_id, caption.text,
get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_message_caption"),
nullptr, std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_message_reply_markup(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
LOG(INFO) << "Begin to edit reply markup of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_reply_markup");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_reply_markup");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!can_edit_message(dialog_id, m, true, true)) {
return promise.set_error(Status::Error(400, "Message can't be edited"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(), nullptr,
std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_inline_message_text(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
if (input_message_content == nullptr) {
return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageText::ID) {
return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText"));
}
auto r_input_message_text =
process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot());
if (r_input_message_text.is_error()) {
return promise.set_error(r_input_message_text.move_as_error());
}
InputMessageText input_message_text = r_input_message_text.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
int32 flags = 0;
if (input_message_text.disable_web_page_preview) {
flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text,
get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities,
"edit_inline_message_text"),
nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_live_location(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::location> &&input_location, int32 heading,
int32 proximity_alert_radius, Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
Location location(input_location);
if (location.empty() && input_location != nullptr) {
return promise.set_error(Status::Error(400, "Invalid location specified"));
}
int32 flags = 0;
if (location.empty()) {
flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK;
}
if (heading != 0) {
flags |= telegram_api::inputMediaGeoLive::HEADING_MASK;
}
flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK;
auto input_media = telegram_api::make_object<telegram_api::inputMediaGeoLive>(
flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius);
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(0, std::move(input_bot_inline_message_id), "", vector<tl_object_ptr<telegram_api::MessageEntity>>(),
std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_media(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
if (input_message_content == nullptr) {
return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageAnimation::ID &&
new_message_content_type != td_api::inputMessageAudio::ID &&
new_message_content_type != td_api::inputMessageDocument::ID &&
new_message_content_type != td_api::inputMessagePhoto::ID &&
new_message_content_type != td_api::inputMessageVideo::ID) {
return promise.set_error(Status::Error(400, "Unsupported input message content type"));
}
auto r_input_message_content = process_input_message_content(DialogId(), std::move(input_message_content));
if (r_input_message_content.is_error()) {
return promise.set_error(r_input_message_content.move_as_error());
}
InputMessageContent content = r_input_message_content.move_as_ok();
if (content.ttl > 0) {
LOG(ERROR) << "Have message content with TTL " << content.ttl;
return promise.set_error(Status::Error(400, "Can't enable self-destruction for media"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
auto input_media = get_input_media(content.content.get(), td_, 0, string(), true);
if (input_media == nullptr) {
return promise.set_error(Status::Error(400, "Invalid message content specified"));
}
const FormattedText *caption = get_message_content_caption(content.content.get());
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(1 << 11, std::move(input_bot_inline_message_id), caption == nullptr ? "" : caption->text,
get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_inline_message_media"),
std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_caption(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::formattedText> &&input_caption,
Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
auto r_caption =
get_formatted_text(td_, DialogId(), std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false);
if (r_caption.is_error()) {
return promise.set_error(r_caption.move_as_error());
}
auto caption = r_caption.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(1 << 11, std::move(input_bot_inline_message_id), caption.text,
get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_inline_message_caption"),
nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_reply_markup(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
CHECK(td_->auth_manager_->is_bot());
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(0, std::move(input_bot_inline_message_id), string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(),
nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_message_scheduling_state(
FullMessageId full_message_id, td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state,
Promise<Unit> &&promise) {
auto r_schedule_date = get_message_schedule_date(std::move(scheduling_state));
if (r_schedule_date.is_error()) {
return promise.set_error(r_schedule_date.move_as_error());
}
auto schedule_date = r_schedule_date.move_as_ok();
LOG(INFO) << "Begin to reschedule " << full_message_id << " to " << schedule_date;
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog_force(dialog_id, "edit_message_scheduling_state");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_scheduling_state");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Message is not scheduled"));
}
if (!m->message_id.is_scheduled_server()) {
return promise.set_error(Status::Error(400, "Can't reschedule the message"));
}
if (get_message_schedule_date(m) == schedule_date) {
return promise.set_value(Unit());
}
m->edited_schedule_date = schedule_date;
if (schedule_date > 0) {
td_->create_handler<EditMessageQuery>(std::move(promise))
->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(), nullptr,
nullptr, schedule_date);
} else {
td_->create_handler<SendScheduledMessageQuery>(std::move(promise))->send(dialog_id, m->message_id);
}
}
bool MessagesManager::is_discussion_message(DialogId dialog_id, const Message *m) const {
if (m == nullptr || m->forward_info == nullptr) {
return false;
}
if (m->sender_user_id.is_valid()) {
if (!td_->auth_manager_->is_bot() || m->sender_user_id != ContactsManager::get_service_notifications_user_id()) {
return false;
}
}
if (!m->forward_info->from_dialog_id.is_valid() || !m->forward_info->from_message_id.is_valid()) {
return false;
}
if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
return false;
}
if (m->forward_info->from_dialog_id == dialog_id) {
return false;
}
if (m->forward_info->from_dialog_id.get_type() != DialogType::Channel) {
return false;
}
return true;
}
bool MessagesManager::has_message_sender_user_id(DialogId dialog_id, const Message *m) const {
if (!m->sender_user_id.is_valid()) {
return false;
}
if (td_->auth_manager_->is_bot() && is_discussion_message(dialog_id, m)) {
return false;
}
return true;
}
int32 MessagesManager::get_message_own_max_media_timestamp(const Message *m) const {
auto duration = get_message_content_media_duration(m->content.get(), td_);
return duration == 0 ? std::numeric_limits<int32>::max() : duration;
}
int32 MessagesManager::get_message_max_media_timestamp(const Message *m) {
return m->max_own_media_timestamp >= 0 ? m->max_own_media_timestamp : m->max_reply_media_timestamp;
}
void MessagesManager::update_message_max_reply_media_timestamp(const Dialog *d, Message *m,
bool need_send_update_message_content) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto new_max_reply_media_timestamp = -1;
if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) {
auto replied_m = get_message(d, m->reply_to_message_id);
if (replied_m != nullptr) {
new_max_reply_media_timestamp = get_message_own_max_media_timestamp(replied_m);
} else if (!is_deleted_message(d, m->reply_to_message_id) &&
m->reply_to_message_id > d->last_clear_history_message_id &&
m->reply_to_message_id > d->max_unavailable_message_id) {
// replied message isn't deleted and isn't loaded yet
return;
}
}
if (m->max_reply_media_timestamp == new_max_reply_media_timestamp) {
return;
}
LOG(INFO) << "Set max_reply_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to "
<< new_max_reply_media_timestamp;
auto old_max_media_timestamp = get_message_max_media_timestamp(m);
m->max_reply_media_timestamp = new_max_reply_media_timestamp;
auto new_max_media_timestamp = get_message_max_media_timestamp(m);
if (need_send_update_message_content && old_max_media_timestamp != new_max_media_timestamp) {
if (old_max_media_timestamp > new_max_media_timestamp) {
std::swap(old_max_media_timestamp, new_max_media_timestamp);
}
if (has_media_timestamps(get_message_content_text(m->content.get()), old_max_media_timestamp + 1,
new_max_media_timestamp)) {
send_update_message_content_impl(d->dialog_id, m, "update_message_max_reply_media_timestamp");
}
}
}
void MessagesManager::update_message_max_own_media_timestamp(const Dialog *d, Message *m) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto new_max_own_media_timestamp = get_message_own_max_media_timestamp(m);
if (m->max_own_media_timestamp == new_max_own_media_timestamp) {
return;
}
LOG(INFO) << "Set max_own_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to "
<< new_max_own_media_timestamp;
m->max_own_media_timestamp = new_max_own_media_timestamp;
update_message_max_reply_media_timestamp_in_replied_messages(d->dialog_id, m->message_id);
}
void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id,
MessageId reply_to_message_id) {
if (reply_to_message_id.is_scheduled()) {
return;
}
CHECK(reply_to_message_id.is_valid());
if (reply_to_message_id.is_yet_unsent()) {
return;
}
FullMessageId full_message_id{dialog_id, reply_to_message_id};
auto it = replied_by_media_timestamp_messages_.find(full_message_id);
if (it == replied_by_media_timestamp_messages_.end()) {
return;
}
LOG(INFO) << "Update max_reply_media_timestamp for replies of " << reply_to_message_id << " in " << dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
for (auto message_id : it->second) {
auto m = get_message(d, message_id);
CHECK(m != nullptr);
CHECK(m->reply_to_message_id == reply_to_message_id);
update_message_max_reply_media_timestamp(d, m, true);
}
}
void MessagesManager::register_message_reply(DialogId dialog_id, const Message *m) {
if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) {
return;
}
if (has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits<int32>::max())) {
LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_message_id;
FullMessageId full_message_id{dialog_id, m->reply_to_message_id};
bool is_inserted = replied_by_media_timestamp_messages_[full_message_id].insert(m->message_id).second;
CHECK(is_inserted);
}
}
void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message *m) {
if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) {
return;
}
auto it = replied_by_media_timestamp_messages_.find({dialog_id, m->reply_to_message_id});
bool was_registered = it != replied_by_media_timestamp_messages_.end() && it->second.count(m->message_id) > 0;
bool need_register =
has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits<int32>::max());
if (was_registered == need_register) {
return;
}
if (was_registered) {
unregister_message_reply(dialog_id, m);
} else {
register_message_reply(dialog_id, m);
}
}
void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message *m) {
auto it = replied_by_media_timestamp_messages_.find({dialog_id, m->reply_to_message_id});
if (it == replied_by_media_timestamp_messages_.end()) {
return;
}
auto is_deleted = it->second.erase(m->message_id) > 0;
if (is_deleted) {
LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_message_id;
if (it->second.empty()) {
replied_by_media_timestamp_messages_.erase(it);
}
}
}
bool MessagesManager::get_message_disable_web_page_preview(const Message *m) {
if (m->content->get_type() != MessageContentType::Text) {
return false;
}
if (has_message_content_web_page(m->content.get())) {
return false;
}
return m->disable_web_page_preview;
}
int32 MessagesManager::get_message_flags(const Message *m) {
int32 flags = 0;
if (m->reply_to_message_id.is_valid()) {
CHECK(m->reply_to_message_id.is_server());
flags |= SEND_MESSAGE_FLAG_IS_REPLY;
}
if (m->top_thread_message_id.is_valid()) {
CHECK(m->top_thread_message_id.is_server());
flags |= SEND_MESSAGE_FLAG_IS_FROM_THREAD;
}
if (m->disable_web_page_preview) {
flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
if (m->reply_markup != nullptr) {
flags |= SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
}
if (m->disable_notification) {
flags |= SEND_MESSAGE_FLAG_DISABLE_NOTIFICATION;
}
if (m->from_background) {
flags |= SEND_MESSAGE_FLAG_FROM_BACKGROUND;
}
if (m->clear_draft) {
flags |= SEND_MESSAGE_FLAG_CLEAR_DRAFT;
}
if (m->message_id.is_scheduled()) {
flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE;
}
if (m->noforwards) {
flags |= SEND_MESSAGE_FLAG_NOFORWARDS;
}
if (m->update_stickersets_order) {
flags |= SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER;
}
return flags;
}
tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_send_message_as_input_peer(const Message *m) const {
if (!m->has_explicit_sender) {
return nullptr;
}
return get_input_peer(get_message_sender(m), AccessRights::Write);
}
bool MessagesManager::can_set_game_score(FullMessageId full_message_id) const {
return can_set_game_score(full_message_id.get_dialog_id(), get_message(full_message_id));
}
bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) const {
if (m == nullptr) {
return false;
}
if (m->content->get_type() != MessageContentType::Game) {
return false;
}
if (m->message_id.is_scheduled()) {
return false;
}
if (m->message_id.is_yet_unsent()) {
return false;
}
if (m->message_id.is_local()) {
return false;
}
if (m->via_bot_user_id.is_valid() && m->via_bot_user_id != td_->contacts_manager_->get_my_id()) {
return false;
}
if (!td_->auth_manager_->is_bot()) {
return false;
}
if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard ||
m->reply_markup->inline_keyboard.empty()) {
return false;
}
switch (dialog_id.get_type()) {
case DialogType::User:
if (!m->is_outgoing && dialog_id != get_my_dialog_id()) {
return false;
}
break;
case DialogType::Chat:
if (!m->is_outgoing) {
return false;
}
break;
case DialogType::Channel: {
if (m->via_bot_user_id.is_valid()) {
// outgoing via_bot messages can always be edited
break;
}
auto channel_id = dialog_id.get_channel_id();
auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (m->is_channel_post) {
if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) {
return false;
}
} else {
if (!m->is_outgoing) {
return false;
}
}
break;
}
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return false;
}
return true;
}
bool MessagesManager::is_forward_info_sender_hidden(const MessageForwardInfo *forward_info) {
CHECK(forward_info != nullptr);
if (forward_info->is_imported) {
return false;
}
if (!forward_info->sender_name.empty()) {
return true;
}
DialogId hidden_sender_dialog_id(ChannelId(static_cast<int64>(G()->is_test_dc() ? 10460537 : 1228946795)));
return forward_info->sender_dialog_id == hidden_sender_dialog_id && !forward_info->author_signature.empty() &&
!forward_info->message_id.is_valid();
}
unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::get_message_forward_info(
tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header, FullMessageId full_message_id) {
if (forward_header == nullptr) {
return nullptr;
}
if (forward_header->date_ <= 0) {
LOG(ERROR) << "Wrong date in message forward header: " << oneline(to_string(forward_header));
return nullptr;
}
auto flags = forward_header->flags_;
DialogId sender_dialog_id;
MessageId message_id;
string author_signature = std::move(forward_header->post_author_);
DialogId from_dialog_id;
MessageId from_message_id;
string sender_name = std::move(forward_header->from_name_);
bool is_imported = forward_header->imported_;
if (forward_header->from_id_ != nullptr) {
sender_dialog_id = DialogId(forward_header->from_id_);
if (!sender_dialog_id.is_valid()) {
LOG(ERROR) << "Receive invalid sender identifier in message forward header: "
<< oneline(to_string(forward_header));
sender_dialog_id = DialogId();
}
}
if ((flags & telegram_api::messageFwdHeader::CHANNEL_POST_MASK) != 0) {
message_id = MessageId(ServerMessageId(forward_header->channel_post_));
if (!message_id.is_valid()) {
LOG(ERROR) << "Receive " << message_id << " in message forward header: " << oneline(to_string(forward_header));
message_id = MessageId();
}
}
if (forward_header->saved_from_peer_ != nullptr) {
from_dialog_id = DialogId(forward_header->saved_from_peer_);
from_message_id = MessageId(ServerMessageId(forward_header->saved_from_msg_id_));
if (!from_dialog_id.is_valid() || !from_message_id.is_valid()) {
LOG(ERROR) << "Receive " << from_message_id << " in " << from_dialog_id
<< " in message forward header: " << oneline(to_string(forward_header));
from_dialog_id = DialogId();
from_message_id = MessageId();
}
}
UserId sender_user_id;
if (sender_dialog_id.get_type() == DialogType::User) {
sender_user_id = sender_dialog_id.get_user_id();
sender_dialog_id = DialogId();
}
if (!sender_dialog_id.is_valid()) {
if (sender_user_id.is_valid()) {
if (message_id.is_valid()) {
LOG(ERROR) << "Receive non-empty message identifier in message forward header: "
<< oneline(to_string(forward_header));
message_id = MessageId();
}
} else if (sender_name.empty()) {
LOG(ERROR) << "Receive wrong message forward header: " << oneline(to_string(forward_header));
return nullptr;
}
} else if (sender_dialog_id.get_type() != DialogType::Channel) {
LOG(ERROR) << "Receive wrong message forward header with non-channel sender: "
<< oneline(to_string(forward_header));
return nullptr;
} else {
auto channel_id = sender_dialog_id.get_channel_id();
if (!td_->contacts_manager_->have_channel(channel_id)) {
LOG(ERROR) << "Receive forward from "
<< (td_->contacts_manager_->have_min_channel(channel_id) ? "min" : "unknown") << ' ' << channel_id
<< " in " << full_message_id;
}
force_create_dialog(sender_dialog_id, "message forward info", true);
CHECK(!sender_user_id.is_valid());
}
if (from_dialog_id.is_valid()) {
force_create_dialog(from_dialog_id, "message forward from info", true);
}
return td::make_unique<MessageForwardInfo>(sender_user_id, forward_header->date_, sender_dialog_id, message_id,
std::move(author_signature), std::move(sender_name), from_dialog_id,
from_message_id, std::move(forward_header->psa_type_), is_imported);
}
td_api::object_ptr<td_api::messageForwardInfo> MessagesManager::get_message_forward_info_object(
const unique_ptr<MessageForwardInfo> &forward_info) const {
if (forward_info == nullptr) {
return nullptr;
}
auto origin = [&]() -> td_api::object_ptr<td_api::MessageForwardOrigin> {
if (forward_info->is_imported) {
return td_api::make_object<td_api::messageForwardOriginMessageImport>(forward_info->sender_name);
}
if (is_forward_info_sender_hidden(forward_info.get())) {
return td_api::make_object<td_api::messageForwardOriginHiddenUser>(
forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name);
}
if (forward_info->message_id.is_valid()) {
return td_api::make_object<td_api::messageForwardOriginChannel>(
forward_info->sender_dialog_id.get(), forward_info->message_id.get(), forward_info->author_signature);
}
if (forward_info->sender_dialog_id.is_valid()) {
return td_api::make_object<td_api::messageForwardOriginChat>(
forward_info->sender_dialog_id.get(),
forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name);
}
return td_api::make_object<td_api::messageForwardOriginUser>(
td_->contacts_manager_->get_user_id_object(forward_info->sender_user_id, "messageForwardOriginUser"));
}();
return td_api::make_object<td_api::messageForwardInfo>(std::move(origin), forward_info->date, forward_info->psa_type,
forward_info->from_dialog_id.get(),
forward_info->from_message_id.get());
}
Result<unique_ptr<ReplyMarkup>> MessagesManager::get_dialog_reply_markup(
DialogId dialog_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr) const {
if (reply_markup_ptr == nullptr) {
return nullptr;
}
auto dialog_type = dialog_id.get_type();
bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr);
bool only_inline_keyboard = is_anonymous;
bool request_buttons_allowed = dialog_type == DialogType::User;
bool switch_inline_buttons_allowed = !is_anonymous;
TRY_RESULT(reply_markup,
get_reply_markup(std::move(reply_markup_ptr), td_->auth_manager_->is_bot(), only_inline_keyboard,
request_buttons_allowed, switch_inline_buttons_allowed));
if (reply_markup == nullptr) {
return nullptr;
}
switch (dialog_type) {
case DialogType::User:
if (reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
reply_markup->is_personal = false;
}
break;
case DialogType::Channel:
case DialogType::Chat:
case DialogType::SecretChat:
case DialogType::None:
// nothing special
break;
default:
UNREACHABLE();
}
return std::move(reply_markup);
}
class MessagesManager::ForwardMessagesLogEvent {
public:
DialogId to_dialog_id;
DialogId from_dialog_id;
vector<MessageId> message_ids;
vector<Message *> messages_in;
bool drop_author;
bool drop_media_captions;
vector<unique_ptr<Message>> messages_out;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(drop_author);
STORE_FLAG(drop_media_captions);
END_STORE_FLAGS();
td::store(to_dialog_id, storer);
td::store(from_dialog_id, storer);
td::store(message_ids, storer);
td::store(messages_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
if (parser.version() >= static_cast<int32>(Version::UseServerForwardAsCopy)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(drop_author);
PARSE_FLAG(drop_media_captions);
END_PARSE_FLAGS();
} else {
drop_author = false;
drop_media_captions = false;
}
td::parse(to_dialog_id, parser);
td::parse(from_dialog_id, parser);
td::parse(message_ids, parser);
td::parse(messages_out, parser);
}
};
uint64 MessagesManager::save_forward_messages_log_event(DialogId to_dialog_id, DialogId from_dialog_id,
const vector<Message *> &messages,
const vector<MessageId> &message_ids, bool drop_author,
bool drop_media_captions) {
ForwardMessagesLogEvent log_event{to_dialog_id, from_dialog_id, message_ids, messages,
drop_author, drop_media_captions, Auto()};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ForwardMessages,
get_log_event_storer(log_event));
}
void MessagesManager::do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id,
const vector<Message *> &messages, const vector<MessageId> &message_ids,
bool drop_author, bool drop_media_captions, uint64 log_event_id) {
if (G()->close_flag()) {
return;
}
CHECK(messages.size() == message_ids.size());
if (messages.empty()) {
return;
}
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_forward_messages_log_event(to_dialog_id, from_dialog_id, messages, message_ids, drop_author,
drop_media_captions);
}
auto schedule_date = get_message_schedule_date(messages[0]);
auto as_input_peer = get_send_message_as_input_peer(messages[0]);
int32 flags = 0;
if (messages[0]->disable_notification) {
flags |= SEND_MESSAGE_FLAG_DISABLE_NOTIFICATION;
}
if (messages[0]->from_background) {
flags |= SEND_MESSAGE_FLAG_FROM_BACKGROUND;
}
if (messages[0]->in_game_share) {
flags |= SEND_MESSAGE_FLAG_WITH_MY_SCORE;
}
if (schedule_date != 0) {
flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE;
}
if (as_input_peer != nullptr) {
flags |= SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
if (messages[0]->noforwards) {
flags |= SEND_MESSAGE_FLAG_NOFORWARDS;
}
if (drop_author) {
flags |= telegram_api::messages_forwardMessages::DROP_AUTHOR_MASK;
}
if (drop_media_captions) {
flags |= telegram_api::messages_forwardMessages::DROP_MEDIA_CAPTIONS_MASK;
}
vector<int64> random_ids =
transform(messages, [this, to_dialog_id](const Message *m) { return begin_send_message(to_dialog_id, m); });
send_closure_later(actor_id(this), &MessagesManager::send_forward_message_query, flags, to_dialog_id,
messages[0]->top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids,
std::move(random_ids), schedule_date, get_erase_log_event_promise(log_event_id));
}
void MessagesManager::send_forward_message_query(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id,
DialogId from_dialog_id,
tl_object_ptr<telegram_api::InputPeer> as_input_peer,
vector<MessageId> message_ids, vector<int64> random_ids,
int32 schedule_date, Promise<Unit> promise) {
td_->create_handler<ForwardMessagesQuery>(std::move(promise))
->send(flags, to_dialog_id, top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids,
std::move(random_ids), schedule_date);
}
Result<td_api::object_ptr<td_api::message>> MessagesManager::forward_message(
DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, MessageId message_id,
tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share, MessageCopyOptions &&copy_options) {
bool need_copy = copy_options.send_copy;
vector<MessageCopyOptions> all_copy_options;
all_copy_options.push_back(std::move(copy_options));
TRY_RESULT(result, forward_messages(to_dialog_id, top_thread_message_id, from_dialog_id, {message_id},
std::move(options), in_game_share, std::move(all_copy_options), false));
CHECK(result->messages_.size() == 1);
if (result->messages_[0] == nullptr) {
return Status::Error(400,
need_copy ? Slice("The message can't be copied") : Slice("The message can't be forwarded"));
}
return std::move(result->messages_[0]);
}
unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::create_message_forward_info(
DialogId from_dialog_id, DialogId to_dialog_id, const Message *forwarded_message) const {
auto content_type = forwarded_message->content->get_type();
if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio) {
return nullptr;
}
auto my_id = td_->contacts_manager_->get_my_id();
auto message_id = forwarded_message->message_id;
DialogId saved_from_dialog_id;
MessageId saved_from_message_id;
if (to_dialog_id == DialogId(my_id)) {
saved_from_dialog_id = from_dialog_id;
saved_from_message_id = message_id;
}
if (forwarded_message->forward_info != nullptr) {
auto forward_info = make_unique<MessageForwardInfo>(*forwarded_message->forward_info);
forward_info->from_dialog_id = saved_from_dialog_id;
forward_info->from_message_id = saved_from_message_id;
return forward_info;
}
if (from_dialog_id != DialogId(my_id) || content_type == MessageContentType::Dice) {
if (forwarded_message->is_channel_post) {
if (is_broadcast_channel(from_dialog_id)) {
auto author_signature = forwarded_message->sender_user_id.is_valid()
? td_->contacts_manager_->get_user_title(forwarded_message->sender_user_id)
: forwarded_message->author_signature;
return td::make_unique<MessageForwardInfo>(UserId(), forwarded_message->date, from_dialog_id,
forwarded_message->message_id, std::move(author_signature), "",
saved_from_dialog_id, saved_from_message_id, "", false);
} else {
LOG(ERROR) << "Don't know how to forward a channel post not from a channel";
}
} else if (forwarded_message->sender_user_id.is_valid() || forwarded_message->sender_dialog_id.is_valid()) {
return td::make_unique<MessageForwardInfo>(
forwarded_message->sender_user_id, forwarded_message->date, forwarded_message->sender_dialog_id, MessageId(),
"", forwarded_message->author_signature, saved_from_dialog_id, saved_from_message_id, "", false);
} else {
LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender";
}
}
return nullptr;
}
void MessagesManager::fix_forwarded_message(Message *m, DialogId to_dialog_id, const Message *forwarded_message,
int64 media_album_id, bool drop_author) const {
if (m->content->get_type() == MessageContentType::Audio) {
drop_author = true;
}
bool is_game = m->content->get_type() == MessageContentType::Game;
if (!drop_author || is_game) {
m->via_bot_user_id = forwarded_message->via_bot_user_id;
}
m->media_album_id = media_album_id;
if (!drop_author && forwarded_message->view_count > 0 && m->forward_info != nullptr && m->view_count == 0 &&
!(m->message_id.is_scheduled() && is_broadcast_channel(to_dialog_id))) {
m->view_count = forwarded_message->view_count;
m->forward_count = forwarded_message->forward_count;
m->interaction_info_update_date = G()->unix_time();
}
if (m->content->get_type() == MessageContentType::Game) {
// via_bot_user_id in games is present unless the message is sent by the bot
if (m->via_bot_user_id == UserId()) {
// if there is no via_bot_user_id, then the original message was sent by the game owner
m->via_bot_user_id = forwarded_message->sender_user_id;
}
if (m->via_bot_user_id == td_->contacts_manager_->get_my_id()) {
// if via_bot_user_id is the current bot user, then there should be
m->via_bot_user_id = UserId();
}
}
if (forwarded_message->reply_markup != nullptr &&
forwarded_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard &&
to_dialog_id.get_type() != DialogType::SecretChat) {
bool need_reply_markup = true;
for (auto &row : forwarded_message->reply_markup->inline_keyboard) {
for (auto &button : row) {
if (button.type == InlineKeyboardButton::Type::Url || button.type == InlineKeyboardButton::Type::UrlAuth) {
// ok
continue;
}
if (m->via_bot_user_id.is_valid() && (button.type == InlineKeyboardButton::Type::SwitchInline ||
button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog)) {
// ok
continue;
}
need_reply_markup = false;
}
}
if (need_reply_markup) {
m->reply_markup = make_unique<ReplyMarkup>(*forwarded_message->reply_markup);
for (auto &row : m->reply_markup->inline_keyboard) {
for (auto &button : row) {
if (button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog) {
button.type = InlineKeyboardButton::Type::SwitchInline;
}
if (!button.forward_text.empty()) {
button.text = std::move(button.forward_text);
button.forward_text.clear();
}
}
}
}
}
}
Result<MessagesManager::ForwardedMessages> MessagesManager::get_forwarded_messages(
DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id,
const vector<MessageId> &message_ids, tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share,
vector<MessageCopyOptions> &&copy_options) {
CHECK(copy_options.size() == message_ids.size());
if (message_ids.size() > 100) { // TODO replace with const from config or implement mass-forward
return Status::Error(400, "Too many messages to forward");
}
if (message_ids.empty()) {
return Status::Error(400, "There are no messages to forward");
}
Dialog *from_dialog = get_dialog_force(from_dialog_id, "forward_messages from");
if (from_dialog == nullptr) {
return Status::Error(400, "Chat to forward messages from not found");
}
if (!have_input_peer(from_dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat to forward messages from");
}
if (from_dialog_id.get_type() == DialogType::SecretChat) {
return Status::Error(400, "Can't forward messages from secret chats");
}
if (get_dialog_has_protected_content(from_dialog_id)) {
for (const auto &copy_option : copy_options) {
if (!copy_option.send_copy || !td_->auth_manager_->is_bot()) {
return Status::Error(400, "Message has protected content and can't be forwarded");
}
}
}
Dialog *to_dialog = get_dialog_force(to_dialog_id, "forward_messages to");
if (to_dialog == nullptr) {
return Status::Error(400, "Chat to forward messages to not found");
}
TRY_STATUS(can_send_message(to_dialog_id));
TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false));
TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageId()));
{
MessageId last_message_id;
for (auto message_id : message_ids) {
if (message_id.is_valid_scheduled()) {
return Status::Error(400, "Can't forward scheduled messages");
}
if (message_id.is_scheduled() || !message_id.is_valid()) {
return Status::Error(400, "Invalid message identifier");
}
if (message_id <= last_message_id) {
return Status::Error(400, "Message identifiers must be in a strictly increasing order");
}
last_message_id = message_id;
}
}
bool to_secret = to_dialog_id.get_type() == DialogType::SecretChat;
bool can_use_server_forward = true;
for (auto &copy_option : copy_options) {
if (!copy_option.is_supported_server_side()) {
can_use_server_forward = false;
break;
}
}
CHECK(can_use_server_forward || copy_options.size() == 1);
if (to_secret) {
can_use_server_forward = false;
}
ForwardedMessages result;
result.to_dialog = to_dialog;
result.from_dialog = from_dialog;
result.message_send_options = message_send_options;
auto &copied_messages = result.copied_messages;
auto &forwarded_message_contents = result.forwarded_message_contents;
result.drop_author = can_use_server_forward && copy_options[0].send_copy;
result.drop_media_captions = can_use_server_forward && copy_options[0].replace_caption;
std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_copied_media_album_ids;
std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_forwarded_media_album_ids;
for (size_t i = 0; i < message_ids.size(); i++) {
MessageId message_id = get_persistent_message_id(from_dialog, message_ids[i]);
const Message *forwarded_message = get_message_force(from_dialog, message_id, "get_forwarded_messages");
if (forwarded_message == nullptr) {
LOG(INFO) << "Can't find " << message_id << " to forward";
continue;
}
CHECK(message_id.is_valid());
CHECK(message_id == forwarded_message->message_id);
if (!can_forward_message(from_dialog_id, forwarded_message)) {
LOG(INFO) << "Can't forward " << message_id;
continue;
}
bool is_broken_server_copy = [&] {
switch (forwarded_message->content->get_type()) {
case MessageContentType::Dice:
return true;
default:
return false;
}
}();
bool need_copy = !message_id.is_server() || to_secret || copy_options[i].send_copy;
bool is_local_copy = need_copy && !(message_id.is_server() && can_use_server_forward && !is_broken_server_copy);
if (!(need_copy && td_->auth_manager_->is_bot()) && !can_save_message(from_dialog_id, forwarded_message)) {
LOG(INFO) << "Forward of " << message_id << " is restricted";
continue;
}
auto type = need_copy ? (is_local_copy ? MessageContentDupType::Copy : MessageContentDupType::ServerCopy)
: MessageContentDupType::Forward;
auto reply_to_message_id = copy_options[i].reply_to_message_id;
auto reply_markup = std::move(copy_options[i].reply_markup);
unique_ptr<MessageContent> content =
dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type, std::move(copy_options[i]));
if (content == nullptr) {
LOG(INFO) << "Can't forward content of " << message_id;
continue;
}
reply_to_message_id = get_reply_to_message_id(to_dialog, top_thread_message_id, reply_to_message_id, false);
auto can_send_status = can_send_message_content(to_dialog_id, content.get(), !is_local_copy, td_);
if (can_send_status.is_error()) {
LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message();
continue;
}
auto can_use_options_status = can_use_message_send_options(message_send_options, content, 0);
if (can_use_options_status.is_error()) {
LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message();
continue;
}
if (can_use_top_thread_message_id(to_dialog, top_thread_message_id, reply_to_message_id).is_error()) {
LOG(INFO) << "Ignore invalid message thread ID " << top_thread_message_id;
top_thread_message_id = MessageId();
}
if (forwarded_message->media_album_id != 0) {
auto &new_media_album_id = is_local_copy ? new_copied_media_album_ids[forwarded_message->media_album_id]
: new_forwarded_media_album_ids[forwarded_message->media_album_id];
new_media_album_id.second++;
if (new_media_album_id.second == 2) { // have at least 2 messages in the new album
CHECK(new_media_album_id.first == 0);
new_media_album_id.first = generate_new_media_album_id();
}
if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) {
CHECK(new_media_album_id.first != 0);
new_media_album_id.first = 0; // just in case
}
}
if (is_local_copy) {
copied_messages.push_back({std::move(content), reply_to_message_id, forwarded_message->message_id,
forwarded_message->reply_to_message_id, std::move(reply_markup),
forwarded_message->media_album_id,
get_message_disable_web_page_preview(forwarded_message), i});
} else {
forwarded_message_contents.push_back({std::move(content), forwarded_message->media_album_id, i});
}
}
result.top_thread_message_id = top_thread_message_id;
if (2 <= forwarded_message_contents.size() && forwarded_message_contents.size() <= MAX_GROUPED_MESSAGES) {
std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
std::unordered_set<DialogId, DialogIdHash> sender_dialog_ids;
for (auto &message_content : forwarded_message_contents) {
message_content_types.insert(message_content.content->get_type());
MessageId message_id = get_persistent_message_id(from_dialog, message_ids[message_content.index]);
sender_dialog_ids.insert(get_message_original_sender(get_message(from_dialog, message_id)));
}
if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin()) &&
sender_dialog_ids.size() == 1 && *sender_dialog_ids.begin() != DialogId()) {
new_forwarded_media_album_ids[0].first = generate_new_media_album_id();
for (auto &message : forwarded_message_contents) {
message.media_album_id = 0;
}
}
}
for (auto &message : forwarded_message_contents) {
message.media_album_id = new_forwarded_media_album_ids[message.media_album_id].first;
}
if (2 <= copied_messages.size() && copied_messages.size() <= MAX_GROUPED_MESSAGES) {
std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
for (auto &copied_message : copied_messages) {
message_content_types.insert(copied_message.content->get_type());
}
if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin())) {
new_copied_media_album_ids[0].first = generate_new_media_album_id();
for (auto &message : copied_messages) {
message.media_album_id = 0;
}
}
}
for (auto &message : copied_messages) {
message.media_album_id = new_copied_media_album_ids[message.media_album_id].first;
}
return std::move(result);
}
Result<td_api::object_ptr<td_api::messages>> MessagesManager::forward_messages(
DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, vector<MessageId> message_ids,
tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share, vector<MessageCopyOptions> &&copy_options,
bool only_preview) {
TRY_RESULT(forwarded_messages_info,
get_forwarded_messages(to_dialog_id, top_thread_message_id, from_dialog_id, message_ids,
std::move(options), in_game_share, std::move(copy_options)));
auto from_dialog = forwarded_messages_info.from_dialog;
auto to_dialog = forwarded_messages_info.to_dialog;
auto message_send_options = forwarded_messages_info.message_send_options;
auto &copied_messages = forwarded_messages_info.copied_messages;
auto &forwarded_message_contents = forwarded_messages_info.forwarded_message_contents;
auto drop_author = forwarded_messages_info.drop_author;
auto drop_media_captions = forwarded_messages_info.drop_media_captions;
top_thread_message_id = forwarded_messages_info.top_thread_message_id;
FlatHashMap<MessageId, MessageId, MessageIdHash> forwarded_message_id_to_new_message_id;
vector<td_api::object_ptr<td_api::message>> result(message_ids.size());
vector<Message *> forwarded_messages;
vector<MessageId> forwarded_message_ids;
bool need_update_dialog_pos = false;
for (size_t j = 0; j < forwarded_message_contents.size(); j++) {
MessageId message_id = get_persistent_message_id(from_dialog, message_ids[forwarded_message_contents[j].index]);
const Message *forwarded_message = get_message(from_dialog, message_id);
CHECK(forwarded_message != nullptr);
auto content = std::move(forwarded_message_contents[j].content);
auto forward_info =
drop_author ? nullptr : create_message_forward_info(from_dialog_id, to_dialog_id, forwarded_message);
if (forward_info != nullptr && !forward_info->is_imported && !is_forward_info_sender_hidden(forward_info.get()) &&
!forward_info->message_id.is_valid() && !forward_info->sender_dialog_id.is_valid() &&
forward_info->sender_user_id.is_valid()) {
auto private_forward_name = td_->contacts_manager_->get_user_private_forward_name(forward_info->sender_user_id);
if (!private_forward_name.empty()) {
forward_info->sender_user_id = UserId();
forward_info->sender_name = std::move(private_forward_name);
}
}
MessageId reply_to_message_id;
if (forwarded_message->reply_to_message_id.is_valid()) {
auto it = forwarded_message_id_to_new_message_id.find(forwarded_message->reply_to_message_id);
if (it != forwarded_message_id_to_new_message_id.end()) {
reply_to_message_id = it->second;
}
}
unique_ptr<Message> message;
Message *m;
if (only_preview) {
message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
std::move(content), j + 1 != forwarded_message_contents.size(),
std::move(forward_info), false, DialogId());
MessageId new_message_id =
message_send_options.schedule_date != 0
? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date)
: get_next_yet_unsent_message_id(to_dialog);
set_message_id(message, new_message_id);
m = message.get();
} else {
m = get_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
std::move(content), &need_update_dialog_pos, j + 1 != forwarded_message_contents.size(),
std::move(forward_info));
}
fix_forwarded_message(m, to_dialog_id, forwarded_message, forwarded_message_contents[j].media_album_id,
drop_author);
m->in_game_share = in_game_share;
m->real_forward_from_dialog_id = from_dialog_id;
m->real_forward_from_message_id = message_id;
forwarded_message_id_to_new_message_id.emplace(message_id, m->message_id);
if (!only_preview) {
send_update_new_message(to_dialog, m);
forwarded_messages.push_back(m);
forwarded_message_ids.push_back(message_id);
}
result[forwarded_message_contents[j].index] = get_message_object(to_dialog_id, m, "forward_messages");
}
if (!forwarded_messages.empty()) {
CHECK(!only_preview);
do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, forwarded_message_ids, drop_author,
drop_media_captions, 0);
}
bool is_secret = to_dialog_id.get_type() == DialogType::SecretChat;
bool is_copy = !is_secret;
for (const auto &copied_message : copied_messages) {
if (forwarded_message_id_to_new_message_id.count(copied_message.original_reply_to_message_id) > 0) {
is_copy = true;
break;
}
forwarded_message_id_to_new_message_id.emplace(copied_message.original_message_id, MessageId());
}
for (auto &copied_message : copied_messages) {
MessageId reply_to_message_id = copied_message.reply_to_message_id;
if (!reply_to_message_id.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) {
auto it = forwarded_message_id_to_new_message_id.find(copied_message.original_reply_to_message_id);
if (it != forwarded_message_id_to_new_message_id.end()) {
reply_to_message_id = it->second;
}
}
unique_ptr<Message> message;
Message *m;
if (only_preview) {
message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
std::move(copied_message.content), false, nullptr, is_copy, DialogId());
MessageId new_message_id =
message_send_options.schedule_date != 0
? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date)
: get_next_yet_unsent_message_id(to_dialog);
set_message_id(message, new_message_id);
m = message.get();
} else {
m = get_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
std::move(copied_message.content), &need_update_dialog_pos, false, nullptr, is_copy);
}
m->disable_web_page_preview = copied_message.disable_web_page_preview;
m->media_album_id = copied_message.media_album_id;
m->reply_markup = std::move(copied_message.reply_markup);
forwarded_message_id_to_new_message_id[copied_message.original_message_id] = m->message_id;
if (!only_preview) {
save_send_message_log_event(to_dialog_id, m);
do_send_message(to_dialog_id, m);
send_update_new_message(to_dialog, m);
}
result[copied_message.index] = get_message_object(to_dialog_id, m, "forward_messages");
}
if (need_update_dialog_pos) {
CHECK(!only_preview);
send_update_chat_last_message(to_dialog, "forward_messages");
}
return get_messages_object(-1, std::move(result), false);
}
Result<vector<MessageId>> MessagesManager::resend_messages(DialogId dialog_id, vector<MessageId> message_ids) {
if (message_ids.empty()) {
return Status::Error(400, "There are no messages to resend");
}
Dialog *d = get_dialog_force(dialog_id, "resend_messages");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
MessageId last_message_id;
for (auto &message_id : message_ids) {
message_id = get_persistent_message_id(d, message_id);
const Message *m = get_message_force(d, message_id, "resend_messages");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (!m->is_failed_to_send) {
return Status::Error(400, "Message is not failed to send");
}
if (!can_resend_message(m)) {
return Status::Error(400, "Message can't be re-sent");
}
if (m->try_resend_at > Time::now()) {
return Status::Error(400, "Message can't be re-sent yet");
}
if (last_message_id != MessageId()) {
if (m->message_id.is_scheduled() != last_message_id.is_scheduled()) {
return Status::Error(400, "Messages must be all scheduled or ordinary");
}
if (m->message_id <= last_message_id) {
return Status::Error(400, "Message identifiers must be in a strictly increasing order");
}
}
last_message_id = m->message_id;
}
vector<unique_ptr<MessageContent>> new_contents(message_ids.size());
std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_media_album_ids;
for (size_t i = 0; i < message_ids.size(); i++) {
MessageId message_id = message_ids[i];
const Message *m = get_message(d, message_id);
CHECK(m != nullptr);
unique_ptr<MessageContent> content =
dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send, MessageCopyOptions());
if (content == nullptr) {
LOG(INFO) << "Can't resend " << m->message_id;
continue;
}
auto can_send_status = can_send_message_content(dialog_id, content.get(), false, td_);
if (can_send_status.is_error()) {
LOG(INFO) << "Can't resend " << m->message_id << ": " << can_send_status.message();
continue;
}
new_contents[i] = std::move(content);
if (m->media_album_id != 0) {
auto &new_media_album_id = new_media_album_ids[m->media_album_id];
new_media_album_id.second++;
if (new_media_album_id.second == 2) { // have at least 2 messages in the new album
CHECK(new_media_album_id.first == 0);
new_media_album_id.first = generate_new_media_album_id();
}
if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) {
CHECK(new_media_album_id.first != 0);
new_media_album_id.first = 0; // just in case
}
}
}
vector<MessageId> result(message_ids.size());
bool need_update_dialog_pos = false;
for (size_t i = 0; i < message_ids.size(); i++) {
if (new_contents[i] == nullptr) {
continue;
}
being_readded_message_id_ = {dialog_id, message_ids[i]};
unique_ptr<Message> message = delete_message(d, message_ids[i], true, &need_update_dialog_pos, "resend_messages");
CHECK(message != nullptr);
send_update_delete_messages(dialog_id, {message->message_id.get()}, true);
auto need_another_sender =
message->send_error_code == 400 && message->send_error_message == CSlice("SEND_AS_PEER_INVALID");
MessageSendOptions options(message->disable_notification, message->from_background,
message->update_stickersets_order, message->noforwards,
get_message_schedule_date(message.get()));
Message *m = get_message_to_send(
d, message->top_thread_message_id,
get_reply_to_message_id(d, message->top_thread_message_id, message->reply_to_message_id, false), options,
std::move(new_contents[i]), &need_update_dialog_pos, false, nullptr, message->is_copy,
need_another_sender ? DialogId() : get_message_sender(message.get()));
m->reply_markup = std::move(message->reply_markup);
m->via_bot_user_id = message->via_bot_user_id;
m->disable_web_page_preview = message->disable_web_page_preview;
m->clear_draft = false; // never clear draft in resend
m->ttl = message->ttl;
m->is_content_secret = message->is_content_secret;
m->media_album_id = new_media_album_ids[message->media_album_id].first;
m->send_emoji = message->send_emoji;
m->has_explicit_sender |= message->has_explicit_sender;
save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
send_update_new_message(d, m);
result[i] = m->message_id;
being_readded_message_id_ = FullMessageId();
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "resend_messages");
}
return result;
}
Status MessagesManager::send_screenshot_taken_notification_message(DialogId dialog_id) {
auto dialog_type = dialog_id.get_type();
if (dialog_type != DialogType::User && dialog_type != DialogType::SecretChat) {
return Status::Error(400, "Notification about taken screenshot can be sent only in private and secret chats");
}
LOG(INFO) << "Begin to send notification about taken screenshot in " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "send_screenshot_taken_notification_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
if (dialog_type == DialogType::User) {
bool need_update_dialog_pos = false;
const Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
create_screenshot_taken_message_content(), &need_update_dialog_pos);
do_send_screenshot_taken_notification_message(dialog_id, m, 0);
send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_screenshot_taken_notification_message");
}
} else {
send_closure(td_->secret_chats_manager_, &SecretChatsManager::notify_screenshot_taken,
dialog_id.get_secret_chat_id(),
Promise<Unit>()); // TODO Promise
}
return Status::OK();
}
class MessagesManager::SendScreenshotTakenNotificationMessageLogEvent {
public:
DialogId dialog_id;
const Message *m_in = nullptr;
unique_ptr<Message> m_out;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id, storer);
td::store(*m_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id, parser);
td::parse(m_out, parser);
}
};
uint64 MessagesManager::save_send_screenshot_taken_notification_message_log_event(DialogId dialog_id,
const Message *m) {
if (!G()->parameters().use_message_db) {
return 0;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
SendScreenshotTakenNotificationMessageLogEvent log_event;
log_event.dialog_id = dialog_id;
log_event.m_in = m;
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendScreenshotTakenNotificationMessage,
get_log_event_storer(log_event));
}
void MessagesManager::do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m,
uint64 log_event_id) {
LOG(INFO) << "Do send screenshot taken notification " << FullMessageId(dialog_id, m->message_id);
CHECK(dialog_id.get_type() == DialogType::User);
if (log_event_id == 0) {
log_event_id = save_send_screenshot_taken_notification_message_log_event(dialog_id, m);
}
int64 random_id = begin_send_message(dialog_id, m);
td_->create_handler<SendScreenshotNotificationQuery>(get_erase_log_event_promise(log_event_id))
->send(dialog_id, random_id);
}
Result<MessageId> MessagesManager::add_local_message(
DialogId dialog_id, td_api::object_ptr<td_api::MessageSender> &&sender, MessageId reply_to_message_id,
bool disable_notification, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
if (input_message_content == nullptr) {
return Status::Error(400, "Can't add local message without content");
}
LOG(INFO) << "Begin to add local message to " << dialog_id << " in reply to " << reply_to_message_id;
Dialog *d = get_dialog_force(dialog_id, "add_local_message");
if (d == nullptr) {
return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
if (message_content.content->get_type() == MessageContentType::Poll) {
return Status::Error(400, "Can't add local poll message");
}
if (message_content.content->get_type() == MessageContentType::Game) {
return Status::Error(400, "Can't add local game message");
}
if (message_content.content->get_type() == MessageContentType::Dice) {
return Status::Error(400, "Can't add local dice message");
}
bool is_channel_post = is_broadcast_channel(dialog_id);
UserId sender_user_id;
DialogId sender_dialog_id;
if (sender != nullptr) {
TRY_RESULT_ASSIGN(sender_dialog_id, get_message_sender_dialog_id(td_, sender, true, false));
auto sender_dialog_type = sender_dialog_id.get_type();
if (sender_dialog_type == DialogType::User) {
sender_user_id = sender_dialog_id.get_user_id();
sender_dialog_id = DialogId();
} else if (sender_dialog_type != DialogType::Channel) {
return Status::Error(400, "Sender chat must be a supergroup or channel");
}
} else if (is_channel_post) {
sender_dialog_id = dialog_id;
} else {
return Status::Error(400, "The message must have a sender");
}
if (is_channel_post && sender_user_id.is_valid()) {
return Status::Error(400, "Channel post can't have a sender user");
}
if (is_channel_post && sender_dialog_id != dialog_id) {
return Status::Error(400, "Channel post must have the channel as a sender");
}
auto dialog_type = dialog_id.get_type();
auto my_id = td_->contacts_manager_->get_my_id();
if (sender_user_id != my_id) {
if (dialog_type == DialogType::User && DialogId(sender_user_id) != dialog_id) {
return Status::Error(400, "Wrong sender user");
}
if (dialog_type == DialogType::SecretChat) {
auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (!peer_user_id.is_valid() || sender_user_id != peer_user_id) {
return Status::Error(400, "Wrong sender user");
}
}
}
MessageId message_id = get_next_local_message_id(d);
auto m = make_unique<Message>();
set_message_id(m, message_id);
if (is_channel_post) {
// sender of the post can be hidden
if (td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) {
m->author_signature = td_->contacts_manager_->get_user_title(sender_user_id);
}
m->sender_dialog_id = sender_dialog_id;
} else {
m->sender_user_id = sender_user_id;
m->sender_dialog_id = sender_dialog_id;
}
m->date = G()->unix_time();
m->reply_to_message_id = get_reply_to_message_id(d, MessageId(), reply_to_message_id, false);
if (m->reply_to_message_id.is_valid() && !message_id.is_scheduled()) {
const Message *reply_m = get_message(d, m->reply_to_message_id);
if (reply_m != nullptr) {
m->top_thread_message_id = reply_m->top_thread_message_id;
if (m->top_thread_message_id.is_valid()) {
m->is_topic_message = reply_m->is_topic_message;
}
}
}
m->is_channel_post = is_channel_post;
m->is_outgoing = dialog_id != DialogId(my_id) && sender_user_id == my_id;
m->disable_notification = disable_notification;
m->from_background = false;
m->update_stickersets_order = false;
m->view_count = 0;
m->forward_count = 0;
m->content = std::move(message_content.content);
m->disable_web_page_preview = message_content.disable_web_page_preview;
m->clear_draft = message_content.clear_draft;
if (dialog_type == DialogType::SecretChat) {
m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id());
if (is_service_message_content(m->content->get_type())) {
m->ttl = 0;
}
} else if (message_content.ttl > 0) {
m->ttl = message_content.ttl;
}
m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
m->send_emoji = std::move(message_content.emoji);
m->have_previous = true;
m->have_next = true;
bool need_update = true;
bool need_update_dialog_pos = false;
auto result =
add_message_to_dialog(d, std::move(m), true, &need_update, &need_update_dialog_pos, "add local message");
LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_;
register_new_local_message_id(d, result);
if (is_message_auto_read(dialog_id, result->is_outgoing)) {
if (result->is_outgoing) {
read_history_outbox(dialog_id, message_id);
} else {
read_history_inbox(dialog_id, message_id, 0, "add_local_message");
}
}
if (result->clear_draft) {
update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
}
send_update_new_message(d, result);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "add_local_message");
}
return message_id;
}
void MessagesManager::get_message_file_type(const string &message_file_head,
Promise<td_api::object_ptr<td_api::MessageFileType>> &&promise) {
td_->create_handler<CheckHistoryImportQuery>(std::move(promise))->send(message_file_head);
}
Status MessagesManager::can_import_messages(DialogId dialog_id) {
if (!have_dialog_force(dialog_id, "can_import_messages")) {
return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
switch (dialog_id.get_type()) {
case DialogType::User:
if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) {
return Status::Error(400, "User must be a mutual contact");
}
break;
case DialogType::Chat:
return Status::Error(400, "Basic groups must be updagraded to supergroups first");
case DialogType::Channel:
if (is_broadcast_channel(dialog_id)) {
return Status::Error(400, "Can't import messages to channels");
}
if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_change_info_and_settings()) {
return Status::Error(400, "Not enough rights to import messages");
}
break;
case DialogType::SecretChat:
return Status::Error(400, "Can't import messages to secret chats");
case DialogType::None:
default:
UNREACHABLE();
}
return Status::OK();
}
void MessagesManager::get_message_import_confirmation_text(DialogId dialog_id, Promise<string> &&promise) {
TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id));
td_->create_handler<CheckHistoryImportPeerQuery>(std::move(promise))->send(dialog_id);
}
void MessagesManager::import_messages(DialogId dialog_id, const td_api::object_ptr<td_api::InputFile> &message_file,
const vector<td_api::object_ptr<td_api::InputFile>> &attached_files,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id));
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, message_file, dialog_id, false, false);
if (r_file_id.is_error()) {
// TODO TRY_RESULT_PROMISE(promise, ...);
return promise.set_error(Status::Error(400, r_file_id.error().message()));
}
FileId file_id = r_file_id.ok();
vector<FileId> attached_file_ids;
attached_file_ids.reserve(attached_files.size());
for (auto &attached_file : attached_files) {
auto file_type = td_->file_manager_->guess_file_type(attached_file);
if (file_type != FileType::Animation && file_type != FileType::Audio && file_type != FileType::Document &&
file_type != FileType::Photo && file_type != FileType::Sticker && file_type != FileType::Video &&
file_type != FileType::VoiceNote) {
LOG(INFO) << "Skip attached file of type " << file_type;
continue;
}
auto r_attached_file_id = td_->file_manager_->get_input_file_id(file_type, attached_file, dialog_id, false, false);
if (r_attached_file_id.is_error()) {
// TODO TRY_RESULT_PROMISE(promise, ...);
return promise.set_error(Status::Error(400, r_attached_file_id.error().message()));
}
attached_file_ids.push_back(r_attached_file_id.ok());
}
upload_imported_messages(dialog_id, td_->file_manager_->dup_file_id(file_id, "import_messages"),
std::move(attached_file_ids), false, std::move(promise));
}
void MessagesManager::upload_imported_messages(DialogId dialog_id, FileId file_id, vector<FileId> attached_file_ids,
bool is_reupload, Promise<Unit> &&promise, vector<int> bad_parts) {
CHECK(file_id.is_valid());
LOG(INFO) << "Ask to upload imported messages file " << file_id;
auto info = td::make_unique<UploadedImportedMessagesInfo>(dialog_id, std::move(attached_file_ids), is_reupload,
std::move(promise));
bool is_inserted = being_uploaded_imported_messages_.emplace(file_id, std::move(info)).second;
CHECK(is_inserted);
// TODO use force_reupload if is_reupload
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_messages_callback_, 1, 0, false,
true);
}
void MessagesManager::start_import_messages(DialogId dialog_id, int64 import_id, vector<FileId> &&attached_file_ids,
Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status());
TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
auto pending_message_import = make_unique<PendingMessageImport>();
pending_message_import->dialog_id = dialog_id;
pending_message_import->import_id = import_id;
pending_message_import->promise = std::move(promise);
auto &multipromise = pending_message_import->upload_files_multipromise;
int64 random_id;
do {
random_id = Random::secure_int64();
} while (random_id == 0 || pending_message_imports_.count(random_id) > 0);
pending_message_imports_[random_id] = std::move(pending_message_import);
multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), random_id](Result<Unit> result) {
send_closure_later(actor_id, &MessagesManager::on_imported_message_attachments_uploaded, random_id,
std::move(result));
}));
auto lock_promise = multipromise.get_promise();
for (auto attached_file_id : attached_file_ids) {
upload_imported_message_attachment(dialog_id, import_id,
td_->file_manager_->dup_file_id(attached_file_id, "start_import_messages"),
false, multipromise.get_promise());
}
lock_promise.set_value(Unit());
}
void MessagesManager::upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id,
bool is_reupload, Promise<Unit> &&promise,
vector<int> bad_parts) {
CHECK(file_id.is_valid());
LOG(INFO) << "Ask to upload improted message attached file " << file_id;
auto info =
td::make_unique<UploadedImportedMessageAttachmentInfo>(dialog_id, import_id, is_reupload, std::move(promise));
bool is_inserted = being_uploaded_imported_message_attachments_.emplace(file_id, std::move(info)).second;
CHECK(is_inserted);
// TODO use force_reupload if is_reupload
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_message_attachment_callback_, 1, 0,
false, true);
}
void MessagesManager::on_imported_message_attachments_uploaded(int64 random_id, Result<Unit> &&result) {
if (G()->close_flag()) {
result = Global::request_aborted_error();
}
auto it = pending_message_imports_.find(random_id);
CHECK(it != pending_message_imports_.end());
auto pending_message_import = std::move(it->second);
CHECK(pending_message_import != nullptr);
pending_message_imports_.erase(it);
if (result.is_error()) {
pending_message_import->promise.set_error(result.move_as_error());
return;
}
CHECK(pending_message_import->upload_files_multipromise.promise_count() == 0);
auto promise = std::move(pending_message_import->promise);
auto dialog_id = pending_message_import->dialog_id;
TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
td_->create_handler<StartImportHistoryQuery>(std::move(promise))->send(dialog_id, pending_message_import->import_id);
}
bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) {
if (!new_message_id.is_valid() && !new_message_id.is_valid_scheduled()) {
LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from "
<< source;
return false;
}
CHECK(new_message_id.is_any_server());
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// update about a new message sent from other device or a service message
LOG(INFO) << "Receive not sent outgoing " << new_message_id << " with random_id = " << random_id;
return true;
}
auto dialog_id = it->second.get_dialog_id();
auto old_message_id = it->second.get_message_id();
being_sent_messages_.erase(it);
if (!have_message_force({dialog_id, old_message_id}, "on_update_message_id")) {
delete_sent_message_on_server(dialog_id, new_message_id, old_message_id);
return true;
}
LOG(INFO) << "Save correspondence from " << new_message_id << " in " << dialog_id << " to " << old_message_id;
CHECK(old_message_id.is_yet_unsent());
if (new_message_id.is_scheduled()) {
update_scheduled_message_ids_[dialog_id][new_message_id.get_scheduled_server_message_id()] = old_message_id;
} else {
update_message_ids_[FullMessageId(dialog_id, new_message_id)] = old_message_id;
}
return true;
}
bool MessagesManager::on_get_dialog_error(DialogId dialog_id, const Status &status, const char *source) {
if (status.message() == CSlice("BOT_METHOD_INVALID")) {
LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source;
return true;
}
if (G()->is_expected_error(status)) {
return true;
}
if (status.message() == CSlice("SEND_AS_PEER_INVALID")) {
reload_dialog_info_full(dialog_id, "SEND_AS_PEER_INVALID");
return true;
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::SecretChat:
// to be implemented if necessary
break;
case DialogType::Channel:
return td_->contacts_manager_->on_get_channel_error(dialog_id.get_channel_id(), status, source);
case DialogType::None:
// to be implemented if necessary
break;
default:
UNREACHABLE();
}
return false;
}
void MessagesManager::on_dialog_updated(DialogId dialog_id, const char *source) {
if (G()->parameters().use_message_db) {
LOG(INFO) << "Update " << dialog_id << " from " << source;
pending_updated_dialog_timeout_.add_timeout_in(dialog_id.get(), MAX_SAVE_DIALOG_DELAY);
}
}
void MessagesManager::send_update_new_message(const Dialog *d, const Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(d->is_update_new_chat_sent);
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateNewMessage>(get_message_object(d->dialog_id, m, "send_update_new_message")));
}
MessagesManager::NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, const Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
return is_from_mention_notification_group(m) ? d->mention_notification_group : d->message_notification_group;
}
NotificationGroupId MessagesManager::get_dialog_notification_group_id(DialogId dialog_id,
NotificationGroupInfo &group_info) {
if (td_->auth_manager_->is_bot()) {
// just in case
return NotificationGroupId();
}
if (!group_info.group_id.is_valid()) {
NotificationGroupId next_notification_group_id;
do {
next_notification_group_id = td_->notification_manager_->get_next_notification_group_id();
if (!next_notification_group_id.is_valid()) {
return NotificationGroupId();
}
} while (get_message_notification_group_force(next_notification_group_id).dialog_id.is_valid());
group_info.group_id = next_notification_group_id;
group_info.is_changed = true;
VLOG(notifications) << "Assign " << next_notification_group_id << " to " << dialog_id;
on_dialog_updated(dialog_id, "get_dialog_notification_group_id");
notification_group_id_to_dialog_id_.emplace(next_notification_group_id, dialog_id);
if (running_get_channel_difference(dialog_id) || get_channel_difference_to_log_event_id_.count(dialog_id) != 0) {
send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
next_notification_group_id);
}
}
CHECK(group_info.group_id.is_valid());
// notification group must be preloaded to guarantee that there is no race between
// get_message_notifications_from_database_force and new notifications added right now
td_->notification_manager_->load_group_force(group_info.group_id);
return group_info.group_id;
}
Result<MessagesManager::MessagePushNotificationInfo> MessagesManager::get_message_push_notification_info(
DialogId dialog_id, MessageId message_id, int64 random_id, UserId sender_user_id, DialogId sender_dialog_id,
int32 date, bool is_from_scheduled, bool contains_mention, bool is_pinned, bool is_from_binlog) {
if (!is_from_scheduled && dialog_id == get_my_dialog_id()) {
return Status::Error("Ignore notification in chat with self");
}
if (td_->auth_manager_->is_bot()) {
return Status::Error("Ignore notification sent to bot");
}
Dialog *d = get_dialog_force(dialog_id, "get_message_push_notification_info");
if (d == nullptr) {
return Status::Error(406, "Ignore notification in unknown chat");
}
if (sender_dialog_id.is_valid() && !have_dialog_force(sender_dialog_id, "get_message_push_notification_info")) {
return Status::Error(406, "Ignore notification sent by unknown chat");
}
if (is_from_scheduled && dialog_id != get_my_dialog_id() &&
td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) {
return Status::Error("Ignore notification about sent scheduled message");
}
bool is_new_pinned = is_pinned && message_id.is_valid() && message_id > d->max_notification_message_id;
CHECK(!message_id.is_scheduled());
if (message_id.is_valid()) {
if (message_id <= d->last_new_message_id) {
return Status::Error("Ignore notification about known message");
}
if (!is_from_binlog && message_id == d->max_notification_message_id) {
return Status::Error("Ignore previously added message push notification");
}
if (!is_from_binlog && message_id < d->max_notification_message_id) {
return Status::Error("Ignore out of order message push notification");
}
if (message_id <= d->last_read_inbox_message_id) {
return Status::Error("Ignore notification about read message");
}
if (message_id <= d->last_clear_history_message_id) {
return Status::Error("Ignore notification about message from cleared chat history");
}
if (is_deleted_message(d, message_id)) {
return Status::Error("Ignore notification about deleted message");
}
if (message_id <= d->max_unavailable_message_id) {
return Status::Error("Ignore notification about unavailable message");
}
}
if (random_id != 0) {
CHECK(dialog_id.get_type() == DialogType::SecretChat);
if (get_message_id_by_random_id(d, random_id, "get_message_push_notification_info").is_valid()) {
return Status::Error(406, "Ignore notification about known secret message");
}
}
if (is_pinned) {
contains_mention = !is_dialog_pinned_message_notifications_disabled(d);
} else if (contains_mention && is_dialog_mention_notifications_disabled(d)) {
contains_mention = false;
}
if (dialog_id.get_type() == DialogType::User) {
contains_mention = false;
}
DialogId settings_dialog_id = dialog_id;
Dialog *settings_dialog = d;
if (contains_mention) {
auto real_sender_dialog_id = sender_dialog_id.is_valid() ? sender_dialog_id : DialogId(sender_user_id);
if (real_sender_dialog_id.is_valid()) {
settings_dialog_id = real_sender_dialog_id;
settings_dialog = get_dialog_force(settings_dialog_id, "get_message_push_notification_info");
}
}
bool have_settings;
int32 mute_until;
std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog);
if (have_settings && mute_until > date) {
if (is_new_pinned) {
remove_dialog_pinned_message_notification(d, "get_message_push_notification_info");
}
return Status::Error("Ignore notification in muted chat");
}
if (is_dialog_message_notification_disabled(settings_dialog_id, date)) {
if (is_new_pinned) {
remove_dialog_pinned_message_notification(d, "get_message_push_notification_info");
}
return Status::Error("Ignore notification in chat, because notifications are disabled in the chat");
}
auto group_id = get_dialog_notification_group_id(
dialog_id, contains_mention ? d->mention_notification_group : d->message_notification_group);
if (!group_id.is_valid()) {
return Status::Error("Can't assign notification group ID");
}
if (message_id.is_valid() && message_id > d->max_notification_message_id) {
if (is_new_pinned) {
set_dialog_pinned_message_notification(d, contains_mention ? message_id : MessageId(),
"get_message_push_notification_info");
}
d->max_notification_message_id = message_id;
on_dialog_updated(dialog_id, "set_max_notification_message_id");
}
MessagePushNotificationInfo result;
result.group_id = group_id;
result.group_type = contains_mention ? NotificationGroupType::Mentions : NotificationGroupType::Messages;
result.settings_dialog_id = settings_dialog_id;
return result;
}
NotificationId MessagesManager::get_next_notification_id(Dialog *d, NotificationGroupId notification_group_id,
MessageId message_id) {
CHECK(d != nullptr);
CHECK(!message_id.is_scheduled());
NotificationId notification_id;
do {
notification_id = td_->notification_manager_->get_next_notification_id();
if (!notification_id.is_valid()) {
return NotificationId();
}
} while (d->notification_id_to_message_id.count(notification_id) != 0 ||
d->new_secret_chat_notification_id == notification_id ||
notification_id.get() <= d->message_notification_group.last_notification_id.get() ||
notification_id.get() <= d->message_notification_group.max_removed_notification_id.get() ||
notification_id.get() <= d->mention_notification_group.last_notification_id.get() ||
notification_id.get() <= d->mention_notification_group.max_removed_notification_id.get()); // just in case
if (message_id.is_valid()) {
add_notification_id_to_message_id_correspondence(d, notification_id, message_id);
}
return notification_id;
}
MessagesManager::MessageNotificationGroup MessagesManager::get_message_notification_group_force(
NotificationGroupId group_id) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(group_id.is_valid());
Dialog *d = nullptr;
auto it = notification_group_id_to_dialog_id_.find(group_id);
if (it != notification_group_id_to_dialog_id_.end()) {
d = get_dialog(it->second);
CHECK(d != nullptr);
} else if (G()->parameters().use_message_db) {
auto *dialog_db = G()->td_db()->get_dialog_db_sync();
dialog_db->begin_read_transaction().ensure();
auto r_value = dialog_db->get_notification_group(group_id);
if (r_value.is_ok()) {
VLOG(notifications) << "Loaded " << r_value.ok() << " from database by " << group_id;
d = get_dialog_force(r_value.ok().dialog_id, "get_message_notification_group_force");
} else {
LOG_CHECK(r_value.error().message() == "Not found") << r_value.error();
VLOG(notifications) << "Failed to load " << group_id << " from database";
}
dialog_db->commit_transaction().ensure();
}
if (d == nullptr) {
return MessageNotificationGroup();
}
if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
if (d->dialog_id.get_type() == DialogType::SecretChat && !d->message_notification_group.group_id.is_valid() &&
!d->mention_notification_group.group_id.is_valid()) {
// the group was reused, but wasn't deleted from the database, trying to resave it
auto &group_info = d->message_notification_group;
group_info.group_id = group_id;
group_info.is_changed = true;
group_info.try_reuse = true;
save_dialog_to_database(d->dialog_id);
group_info.group_id = NotificationGroupId();
group_info.is_changed = false;
group_info.try_reuse = false;
}
}
LOG_CHECK(d->message_notification_group.group_id == group_id || d->mention_notification_group.group_id == group_id);
bool from_mentions = d->mention_notification_group.group_id == group_id;
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
MessageNotificationGroup result;
VLOG(notifications) << "Found " << (from_mentions ? "Mentions " : "Messages ") << group_info.group_id << '/'
<< d->dialog_id << " by " << group_id << " with " << d->unread_mention_count
<< " unread mentions, " << d->unread_reaction_count << " unread reactions, pinned "
<< d->pinned_message_notification_message_id << ", new secret chat "
<< d->new_secret_chat_notification_id << " and " << d->server_unread_count + d->local_unread_count
<< " unread messages";
result.dialog_id = d->dialog_id;
result.total_count = get_dialog_pending_notification_count(d, from_mentions);
auto pending_notification_count =
from_mentions ? d->pending_new_mention_notifications.size() : d->pending_new_message_notifications.size();
result.total_count -= static_cast<int32>(pending_notification_count);
if (result.total_count < 0) {
LOG(ERROR) << "Total notification count is " << result.total_count << " in " << d->dialog_id << " with "
<< pending_notification_count << " pending new notifications";
result.total_count = 0;
}
if (d->new_secret_chat_notification_id.is_valid()) {
CHECK(d->dialog_id.get_type() == DialogType::SecretChat);
result.type = NotificationGroupType::SecretChat;
result.notifications.emplace_back(d->new_secret_chat_notification_id,
td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()),
false, create_new_secret_chat_notification());
} else {
result.type = from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages;
result.notifications = get_message_notifications_from_database_force(
d, from_mentions, static_cast<int32>(td_->notification_manager_->get_max_notification_group_size()));
}
int32 last_notification_date = 0;
NotificationId last_notification_id;
if (!result.notifications.empty()) {
last_notification_date = result.notifications[0].date;
last_notification_id = result.notifications[0].notification_id;
}
if (last_notification_date != group_info.last_notification_date ||
last_notification_id != group_info.last_notification_id) {
LOG(ERROR) << "Fix last notification date in " << d->dialog_id << " from " << group_info.last_notification_date
<< " to " << last_notification_date << " and last notification identifier from "
<< group_info.last_notification_id << " to " << last_notification_id << " in " << group_id << " of type "
<< result.type;
set_dialog_last_notification(d->dialog_id, group_info, last_notification_date, last_notification_id,
"get_message_notification_group_force");
}
std::reverse(result.notifications.begin(), result.notifications.end());
return result;
}
bool MessagesManager::get_dialog_show_preview(const Dialog *d) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
if (d->notification_settings.use_default_show_preview) {
auto scope = get_dialog_notification_setting_scope(d->dialog_id);
return td_->notification_settings_manager_->get_scope_show_preview(scope);
} else {
return d->notification_settings.show_preview;
}
}
bool MessagesManager::is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions) {
if (!get_dialog_show_preview(d)) {
return false;
}
if (!from_mentions) {
return true;
}
auto sender_dialog_id = get_message_sender(m);
if (!sender_dialog_id.is_valid()) {
return true;
}
d = get_dialog_force(sender_dialog_id, "is_message_preview_enabled");
if (d == nullptr) {
auto scope = get_dialog_notification_setting_scope(sender_dialog_id);
return td_->notification_settings_manager_->get_scope_show_preview(scope);
}
return get_dialog_show_preview(d);
}
bool MessagesManager::is_from_mention_notification_group(const Message *m) {
return m->contains_mention && !m->is_mention_notification_disabled;
}
bool MessagesManager::is_message_notification_active(const Dialog *d, const Message *m) {
CHECK(!m->message_id.is_scheduled());
if (is_from_mention_notification_group(m)) {
return m->notification_id.get() > d->mention_notification_group.max_removed_notification_id.get() &&
m->message_id > d->mention_notification_group.max_removed_message_id &&
(m->contains_unread_mention || m->message_id == d->pinned_message_notification_message_id);
} else {
return m->notification_id.get() > d->message_notification_group.max_removed_notification_id.get() &&
m->message_id > d->message_notification_group.max_removed_message_id &&
m->message_id > d->last_read_inbox_message_id;
}
}
void MessagesManager::try_add_pinned_message_notification(Dialog *d, vector<Notification> &res,
NotificationId max_notification_id, int32 limit) {
CHECK(d != nullptr);
auto message_id = d->pinned_message_notification_message_id;
if (!message_id.is_valid() || message_id > d->last_new_message_id) {
CHECK(!message_id.is_scheduled());
return;
}
auto m = get_message_force(d, message_id, "try_add_pinned_message_notification");
if (m != nullptr && m->notification_id.get() > d->mention_notification_group.max_removed_notification_id.get() &&
m->message_id > d->mention_notification_group.max_removed_message_id &&
m->message_id > d->last_read_inbox_message_id && !is_dialog_pinned_message_notifications_disabled(d)) {
if (m->notification_id.get() < max_notification_id.get()) {
VLOG(notifications) << "Add " << m->notification_id << " about pinned " << message_id << " in " << d->dialog_id;
auto pinned_message_id = get_message_content_pinned_message_id(m->content.get());
if (pinned_message_id.is_valid()) {
get_message_force(d, pinned_message_id, "try_add_pinned_message_notification 2"); // preload pinned message
}
auto pos = res.size();
res.emplace_back(m->notification_id, m->date, m->disable_notification,
create_new_message_notification(message_id, is_message_preview_enabled(d, m, true)));
while (pos > 0 && res[pos - 1].type->get_message_id() < message_id) {
std::swap(res[pos - 1], res[pos]);
pos--;
}
if (pos > 0 && res[pos - 1].type->get_message_id() == message_id) {
res.erase(res.begin() + pos); // notification was already there
}
if (res.size() > static_cast<size_t>(limit)) {
res.pop_back();
CHECK(res.size() == static_cast<size_t>(limit));
}
}
} else {
remove_dialog_pinned_message_notification(d, "try_add_pinned_message_notification");
}
}
vector<Notification> MessagesManager::get_message_notifications_from_database_force(Dialog *d, bool from_mentions,
int32 limit) {
CHECK(d != nullptr);
if (!G()->parameters().use_message_db || td_->auth_manager_->is_bot()) {
return {};
}
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
auto from_notification_id = NotificationId::max();
auto from_message_id = MessageId::max();
vector<Notification> res;
if (!from_mentions && from_message_id <= d->last_read_inbox_message_id) {
return res;
}
while (true) {
auto messages = do_get_message_notifications_from_database_force(d, from_mentions, from_notification_id,
from_message_id, limit);
if (messages.empty()) {
break;
}
bool is_found = false;
VLOG(notifications) << "Loaded " << messages.size() << (from_mentions ? " mention" : "")
<< " messages with notifications from database in " << group_info.group_id << '/'
<< d->dialog_id;
for (auto &message : messages) {
auto m = on_get_message_from_database(d, message, false, "get_message_notifications_from_database_force");
if (m == nullptr) {
VLOG(notifications) << "Receive from database a broken message";
continue;
}
auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id;
if (!notification_id.is_valid()) {
LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id
<< " with from_mentions = " << from_mentions;
continue;
}
CHECK(m->message_id.is_valid());
bool is_correct = true;
if (notification_id.get() >= from_notification_id.get()) {
// possible if two messages have the same notification_id
LOG(ERROR) << "Have nonmonotonic notification identifiers: " << d->dialog_id << " " << m->message_id << " "
<< notification_id << " " << from_message_id << " " << from_notification_id;
is_correct = false;
} else {
from_notification_id = notification_id;
is_found = true;
}
if (m->message_id >= from_message_id) {
LOG(ERROR) << "Have nonmonotonic message identifiers: " << d->dialog_id << " " << m->message_id << " "
<< notification_id << " " << from_message_id << " " << from_notification_id;
is_correct = false;
} else {
from_message_id = m->message_id;
is_found = true;
}
if (notification_id.get() <= group_info.max_removed_notification_id.get() ||
m->message_id <= group_info.max_removed_message_id ||
(!from_mentions && m->message_id <= d->last_read_inbox_message_id)) {
// if message still has notification_id, but it was removed via max_removed_notification_id,
// or max_removed_message_id, or last_read_inbox_message_id,
// then there will be no more messages with active notifications
is_found = false;
break;
}
if (!m->notification_id.is_valid()) {
// notification_id can be empty if it is deleted in memory, but not in the database
VLOG(notifications) << "Receive from database " << m->message_id << " with removed "
<< m->removed_notification_id;
continue;
}
if (is_from_mention_notification_group(m) != from_mentions) {
VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id
<< " from another group";
continue;
}
if (!is_message_notification_active(d, m)) {
CHECK(from_mentions);
CHECK(!m->contains_unread_mention);
CHECK(m->message_id != d->pinned_message_notification_message_id);
// skip read mentions
continue;
}
if (is_correct) {
// skip mention messages returned among unread messages
res.emplace_back(
m->notification_id, m->date, m->disable_notification,
create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)));
} else {
remove_message_notification_id(d, m, true, false);
on_message_changed(d, m, false, "get_message_notifications_from_database_force");
}
}
if (!res.empty() || !is_found) {
break;
}
}
if (from_mentions) {
try_add_pinned_message_notification(d, res, NotificationId::max(), limit);
}
return res;
}
vector<MessageDbDialogMessage> MessagesManager::do_get_message_notifications_from_database_force(
Dialog *d, bool from_mentions, NotificationId from_notification_id, MessageId from_message_id, int32 limit) {
CHECK(G()->parameters().use_message_db);
CHECK(!from_message_id.is_scheduled());
auto *db = G()->td_db()->get_message_db_sync();
if (!from_mentions) {
CHECK(from_message_id > d->last_read_inbox_message_id);
VLOG(notifications) << "Trying to load " << limit << " messages with notifications in "
<< d->message_notification_group.group_id << '/' << d->dialog_id << " from "
<< from_notification_id;
return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit);
} else {
VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in "
<< d->mention_notification_group.group_id << '/' << d->dialog_id << " from " << from_message_id;
// ignore first_db_message_id, notifications can be nonconsecutive
MessageDbMessagesQuery db_query;
db_query.dialog_id = d->dialog_id;
db_query.filter = MessageSearchFilter::UnreadMention;
db_query.from_message_id = from_message_id;
db_query.offset = 0;
db_query.limit = limit;
return db->get_messages(db_query);
}
}
vector<NotificationGroupKey> MessagesManager::get_message_notification_group_keys_from_database(
NotificationGroupKey from_group_key, int32 limit) {
if (!G()->parameters().use_message_db) {
return {};
}
VLOG(notifications) << "Trying to load " << limit << " message notification groups from database from "
<< from_group_key;
auto *dialog_db = G()->td_db()->get_dialog_db_sync();
dialog_db->begin_read_transaction().ensure();
auto group_keys = dialog_db->get_notification_groups_by_last_notification_date(from_group_key, limit);
vector<NotificationGroupKey> result;
for (auto &group_key : group_keys) {
CHECK(group_key.group_id.is_valid());
CHECK(group_key.dialog_id.is_valid());
const Dialog *d = get_dialog_force(group_key.dialog_id, "get_message_notification_group_keys_from_database");
if (d == nullptr || (d->message_notification_group.group_id != group_key.group_id &&
d->mention_notification_group.group_id != group_key.group_id)) {
continue;
}
CHECK(d->dialog_id == group_key.dialog_id);
CHECK(notification_group_id_to_dialog_id_[group_key.group_id] == d->dialog_id);
VLOG(notifications) << "Loaded " << group_key << " from database";
result.push_back(group_key);
}
dialog_db->commit_transaction().ensure();
return result;
}
void MessagesManager::get_message_notifications_from_database(DialogId dialog_id, NotificationGroupId group_id,
NotificationId from_notification_id,
MessageId from_message_id, int32 limit,
Promise<vector<Notification>> promise) {
if (!G()->parameters().use_message_db) {
return promise.set_error(Status::Error(500, "There is no message database"));
}
if (td_->auth_manager_->is_bot()) {
return promise.set_error(Status::Error(500, "Bots have no notifications"));
}
CHECK(dialog_id.is_valid());
CHECK(group_id.is_valid());
CHECK(!from_message_id.is_scheduled());
CHECK(limit > 0);
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
return promise.set_value(vector<Notification>());
}
VLOG(notifications) << "Get " << limit << " message notifications from database in " << group_id << " from "
<< dialog_id << " from " << from_notification_id << "/" << from_message_id;
bool from_mentions = d->mention_notification_group.group_id == group_id;
if (d->new_secret_chat_notification_id.is_valid()) {
CHECK(dialog_id.get_type() == DialogType::SecretChat);
vector<Notification> notifications;
if (!from_mentions && d->new_secret_chat_notification_id.get() < from_notification_id.get()) {
auto date = td_->contacts_manager_->get_secret_chat_date(dialog_id.get_secret_chat_id());
if (date <= 0) {
remove_new_secret_chat_notification(d, true);
} else {
notifications.emplace_back(d->new_secret_chat_notification_id, date, false,
create_new_secret_chat_notification());
}
}
return promise.set_value(std::move(notifications));
}
do_get_message_notifications_from_database(d, from_mentions, from_notification_id, from_notification_id,
from_message_id, limit, std::move(promise));
}
void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool from_mentions,
NotificationId initial_from_notification_id,
NotificationId from_notification_id,
MessageId from_message_id, int32 limit,
Promise<vector<Notification>> promise) {
CHECK(G()->parameters().use_message_db);
CHECK(!from_message_id.is_scheduled());
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (from_notification_id.get() <= group_info.max_removed_notification_id.get() ||
from_message_id <= group_info.max_removed_message_id ||
(!from_mentions && from_message_id <= d->last_read_inbox_message_id)) {
return promise.set_value(vector<Notification>());
}
auto dialog_id = d->dialog_id;
auto new_promise =
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_mentions, initial_from_notification_id, limit,
promise = std::move(promise)](Result<vector<MessageDbDialogMessage>> result) mutable {
send_closure(actor_id, &MessagesManager::on_get_message_notifications_from_database, dialog_id, from_mentions,
initial_from_notification_id, limit, std::move(result), std::move(promise));
});
auto *db = G()->td_db()->get_message_db_async();
if (!from_mentions) {
VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << group_info.group_id
<< '/' << dialog_id << " from " << from_notification_id;
return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit, std::move(new_promise));
} else {
VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " << group_info.group_id
<< '/' << dialog_id << " from " << from_message_id;
// ignore first_db_message_id, notifications can be nonconsecutive
MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.filter = MessageSearchFilter::UnreadMention;
db_query.from_message_id = from_message_id;
db_query.offset = 0;
db_query.limit = limit;
return db->get_messages(db_query, std::move(new_promise));
}
}
void MessagesManager::on_get_message_notifications_from_database(DialogId dialog_id, bool from_mentions,
NotificationId initial_from_notification_id,
int32 limit,
Result<vector<MessageDbDialogMessage>> result,
Promise<vector<Notification>> promise) {
if (G()->close_flag()) {
result = Global::request_aborted_error();
}
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (!group_info.group_id.is_valid()) {
return promise.set_error(Status::Error("Notification group was deleted"));
}
auto messages = result.move_as_ok();
vector<Notification> res;
res.reserve(messages.size());
NotificationId from_notification_id;
MessageId from_message_id;
VLOG(notifications) << "Loaded " << messages.size() << " messages with notifications in " << group_info.group_id
<< '/' << dialog_id << " from database";
for (auto &message : messages) {
auto m = on_get_message_from_database(d, message, false, "on_get_message_notifications_from_database");
if (m == nullptr) {
VLOG(notifications) << "Receive from database a broken message";
continue;
}
auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id;
if (!notification_id.is_valid()) {
LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id
<< " with from_mentions = " << from_mentions;
continue;
}
CHECK(m->message_id.is_valid());
bool is_correct = true;
if (from_notification_id.is_valid() && notification_id.get() >= from_notification_id.get()) {
LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/"
<< from_notification_id;
is_correct = false;
} else {
from_notification_id = notification_id;
}
if (from_message_id.is_valid() && m->message_id >= from_message_id) {
LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/"
<< from_notification_id;
is_correct = false;
} else {
from_message_id = m->message_id;
}
if (notification_id.get() <= group_info.max_removed_notification_id.get() ||
m->message_id <= group_info.max_removed_message_id ||
(!from_mentions && m->message_id <= d->last_read_inbox_message_id)) {
// if message still has notification_id, but it was removed via max_removed_notification_id,
// or max_removed_message_id, or last_read_inbox_message_id,
// then there will be no more messages with active notifications
from_notification_id = NotificationId(); // stop requesting database
break;
}
if (!m->notification_id.is_valid()) {
// notification_id can be empty if it is deleted in memory, but not in the database
VLOG(notifications) << "Receive from database " << m->message_id << " with removed "
<< m->removed_notification_id;
continue;
}
if (is_from_mention_notification_group(m) != from_mentions) {
VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id
<< " from another category";
continue;
}
if (!is_message_notification_active(d, m)) {
CHECK(from_mentions);
CHECK(!m->contains_unread_mention);
CHECK(m->message_id != d->pinned_message_notification_message_id);
// skip read mentions
continue;
}
if (is_correct) {
// skip mention messages returned among unread messages
CHECK(m->date > 0);
res.emplace_back(m->notification_id, m->date, m->disable_notification,
create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)));
} else {
remove_message_notification_id(d, m, true, false);
on_message_changed(d, m, false, "on_get_message_notifications_from_database");
}
}
if (!res.empty() || !from_notification_id.is_valid() || static_cast<size_t>(limit) > messages.size()) {
if (from_mentions) {
try_add_pinned_message_notification(d, res, initial_from_notification_id, limit);
}
std::reverse(res.begin(), res.end());
return promise.set_value(std::move(res));
}
// try again from adjusted from_notification_id and from_message_id
do_get_message_notifications_from_database(d, from_mentions, initial_from_notification_id, from_notification_id,
from_message_id, limit, std::move(promise));
}
void MessagesManager::remove_message_notification(DialogId dialog_id, NotificationGroupId group_id,
NotificationId notification_id) {
Dialog *d = get_dialog_force(dialog_id, "remove_message_notification");
if (d == nullptr) {
LOG(ERROR) << "Can't find " << dialog_id;
return;
}
if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
LOG(ERROR) << "There is no " << group_id << " in " << dialog_id;
return;
}
if (notification_id == NotificationId::max() || !notification_id.is_valid()) {
return; // there can be no notification with this ID
}
bool from_mentions = d->mention_notification_group.group_id == group_id;
if (d->new_secret_chat_notification_id.is_valid()) {
if (!from_mentions && d->new_secret_chat_notification_id == notification_id) {
return remove_new_secret_chat_notification(d, false);
}
return;
}
auto it = d->notification_id_to_message_id.find(notification_id);
if (it != d->notification_id_to_message_id.end()) {
auto m = get_message(d, it->second);
CHECK(m != nullptr);
CHECK(m->notification_id == notification_id);
CHECK(!m->message_id.is_scheduled());
if (is_from_mention_notification_group(m) == from_mentions && is_message_notification_active(d, m)) {
remove_message_notification_id(d, m, false, false);
}
return;
}
if (G()->parameters().use_message_db) {
G()->td_db()->get_message_db_async()->get_messages_from_notification_id(
dialog_id, NotificationId(notification_id.get() + 1), 1,
PromiseCreator::lambda([dialog_id, from_mentions, notification_id,
actor_id = actor_id(this)](vector<MessageDbDialogMessage> result) {
send_closure(actor_id, &MessagesManager::do_remove_message_notification, dialog_id, from_mentions,
notification_id, std::move(result));
}));
}
}
void MessagesManager::remove_message_notifications_by_message_ids(DialogId dialog_id,
const vector<MessageId> &message_ids) {
VLOG(notifications) << "Trying to remove notification about " << message_ids << " in " << dialog_id;
Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications_by_message_ids");
if (d == nullptr) {
return;
}
bool need_update_dialog_pos = false;
vector<int64> deleted_message_ids;
for (auto message_id : message_ids) {
CHECK(!message_id.is_scheduled());
// can't remove just notification_id, because total_count will stay wrong after restart
// delete whole message
auto message =
delete_message(d, message_id, true, &need_update_dialog_pos, "remove_message_notifications_by_message_ids");
if (message == nullptr) {
LOG(INFO) << "Can't delete " << message_id << " because it is not found";
// call synchronously to remove them before ProcessPush returns
td_->notification_manager_->remove_temporary_notification_by_message_id(
d->message_notification_group.group_id, message_id, true, "remove_message_notifications_by_message_ids");
td_->notification_manager_->remove_temporary_notification_by_message_id(
d->mention_notification_group.group_id, message_id, true, "remove_message_notifications_by_message_ids");
continue;
}
deleted_message_ids.push_back(message->message_id.get());
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "remove_message_notifications_by_message_ids");
}
send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true);
}
void MessagesManager::do_remove_message_notification(DialogId dialog_id, bool from_mentions,
NotificationId notification_id,
vector<MessageDbDialogMessage> result) {
if (result.empty() || G()->close_flag()) {
return;
}
CHECK(result.size() == 1);
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto m = on_get_message_from_database(d, result[0], false, "do_remove_message_notification");
if (m != nullptr && m->notification_id == notification_id && is_from_mention_notification_group(m) == from_mentions &&
is_message_notification_active(d, m)) {
remove_message_notification_id(d, m, false, false);
}
}
void MessagesManager::remove_message_notifications(DialogId dialog_id, NotificationGroupId group_id,
NotificationId max_notification_id, MessageId max_message_id) {
Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications");
if (d == nullptr) {
LOG(ERROR) << "Can't find " << dialog_id;
return;
}
if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
LOG(ERROR) << "There is no " << group_id << " in " << dialog_id;
return;
}
if (!max_notification_id.is_valid()) {
return;
}
CHECK(!max_message_id.is_scheduled());
bool from_mentions = d->mention_notification_group.group_id == group_id;
if (d->new_secret_chat_notification_id.is_valid()) {
if (!from_mentions && d->new_secret_chat_notification_id.get() <= max_notification_id.get()) {
return remove_new_secret_chat_notification(d, false);
}
return;
}
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (max_notification_id.get() <= group_info.max_removed_notification_id.get()) {
return;
}
if (max_message_id > group_info.max_removed_message_id) {
VLOG(notifications) << "Set max_removed_message_id in " << group_info.group_id << '/' << dialog_id << " to "
<< max_message_id;
group_info.max_removed_message_id = max_message_id.get_prev_server_message_id();
}
VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << dialog_id << " to "
<< max_notification_id;
group_info.max_removed_notification_id = max_notification_id;
on_dialog_updated(dialog_id, "remove_message_notifications");
if (group_info.last_notification_id.is_valid() &&
max_notification_id.get() >= group_info.last_notification_id.get()) {
bool is_changed =
set_dialog_last_notification(dialog_id, group_info, 0, NotificationId(), "remove_message_notifications");
CHECK(is_changed);
}
}
int32 MessagesManager::get_dialog_pending_notification_count(const Dialog *d, bool from_mentions) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
if (from_mentions) {
bool has_pinned_message = d->pinned_message_notification_message_id.is_valid() &&
d->pinned_message_notification_message_id <= d->last_new_message_id;
return d->unread_mention_count + static_cast<int32>(has_pinned_message);
} else {
if (d->new_secret_chat_notification_id.is_valid()) {
return 1;
}
if (is_dialog_muted(d)) {
return narrow_cast<int32>(d->pending_new_message_notifications.size()); // usually 0
}
return d->server_unread_count + d->local_unread_count;
}
}
void MessagesManager::update_dialog_mention_notification_count(const Dialog *d) {
CHECK(d != nullptr);
if (td_->auth_manager_->is_bot() || !d->mention_notification_group.group_id.is_valid()) {
return;
}
auto total_count =
get_dialog_pending_notification_count(d, true) - static_cast<int32>(d->pending_new_mention_notifications.size());
if (total_count < 0) {
LOG(ERROR) << "Total mention notification count is " << total_count << " in " << d->dialog_id << " with "
<< d->pending_new_mention_notifications << " pending new mention notifications";
total_count = 0;
}
send_closure_later(G()->notification_manager(), &NotificationManager::set_notification_total_count,
d->mention_notification_group.group_id, total_count);
}
bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Message *m) const {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (!has_incoming_notification(d->dialog_id, m) || td_->auth_manager_->is_bot()) {
return true;
}
if (m->is_from_scheduled && d->dialog_id != get_my_dialog_id() &&
td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) {
return true;
}
if (m->forward_info != nullptr && m->forward_info->is_imported) {
return true;
}
switch (m->content->get_type()) {
case MessageContentType::ChatDeleteHistory:
case MessageContentType::ChatMigrateTo:
case MessageContentType::Unsupported:
case MessageContentType::ExpiredPhoto:
case MessageContentType::ExpiredVideo:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::WebViewDataSent:
case MessageContentType::WebViewDataReceived:
VLOG(notifications) << "Disable notification for " << m->message_id << " in " << d->dialog_id
<< " with content of type " << m->content->get_type();
return true;
case MessageContentType::ContactRegistered:
if (m->disable_notification) {
return true;
}
break;
default:
break;
}
return is_dialog_message_notification_disabled(d->dialog_id, m->date);
}
bool MessagesManager::is_dialog_message_notification_disabled(DialogId dialog_id, int32 message_date) const {
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) {
return true;
}
break;
case DialogType::Channel:
if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_member() ||
message_date < td_->contacts_manager_->get_channel_date(dialog_id.get_channel_id())) {
return true;
}
break;
case DialogType::SecretChat:
if (td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Closed) {
return true;
}
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (message_date < authorization_date_) {
return true;
}
return false;
}
bool MessagesManager::may_need_message_notification(const Dialog *d, const Message *m) const {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
if (is_message_notification_disabled(d, m)) {
return false;
}
if (is_from_mention_notification_group(m)) {
return true;
}
bool have_settings;
int32 mute_until;
std::tie(have_settings, mute_until) = get_dialog_mute_until(d->dialog_id, d);
return !have_settings || mute_until <= m->date;
}
bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool force) {
CHECK(d != nullptr);
CHECK(m != nullptr);
CHECK(m->message_id.is_valid());
if (!force) {
if (d->message_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications,
d->message_notification_group.group_id, "add_new_message_notification 1");
}
if (d->mention_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications,
d->mention_notification_group.group_id, "add_new_message_notification 2");
}
}
CHECK(!m->notification_id.is_valid());
if (is_message_notification_disabled(d, m)) {
return false;
}
auto from_mentions = is_from_mention_notification_group(m);
bool is_pinned = m->content->get_type() == MessageContentType::PinMessage;
bool is_active =
from_mentions ? m->contains_unread_mention || is_pinned : m->message_id > d->last_read_inbox_message_id;
if (is_active) {
auto &group = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (group.max_removed_message_id >= m->message_id) {
is_active = false;
}
}
if (!is_active) {
VLOG(notifications) << "Disable inactive notification for " << m->message_id << " in " << d->dialog_id;
if (is_pinned) {
remove_dialog_pinned_message_notification(d, "add_new_message_notification");
}
return false;
}
VLOG(notifications) << "Trying to " << (force ? "forcely " : "") << "add new message notification for "
<< m->message_id << " in " << d->dialog_id
<< (m->disable_notification ? " silently" : " with sound");
DialogId settings_dialog_id = d->dialog_id;
Dialog *settings_dialog = d;
if (is_from_mention_notification_group(m)) {
// have a mention, so use notification settings from the dialog with the sender
auto sender_dialog_id = get_message_sender(m);
if (sender_dialog_id.is_valid()) {
settings_dialog_id = sender_dialog_id;
settings_dialog = get_dialog_force(settings_dialog_id, "add_new_message_notification");
}
}
bool have_settings;
int32 mute_until;
std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog);
if (mute_until > m->date && (have_settings || force)) {
VLOG(notifications) << "Disable notification, because " << settings_dialog_id << " is muted";
if (is_pinned) {
remove_dialog_pinned_message_notification(d, "add_new_message_notification");
}
return false;
}
MessageId missing_pinned_message_id;
if (is_pinned) {
auto message_id = get_message_content_pinned_message_id(m->content.get());
if (message_id.is_valid() &&
!have_message_force(d, message_id,
force ? "add_new_message_notification force" : "add_new_message_notification not force")) {
missing_pinned_message_id = message_id;
}
}
auto &pending_notifications =
from_mentions ? d->pending_new_mention_notifications : d->pending_new_message_notifications;
if (!force && (!have_settings || !pending_notifications.empty() || missing_pinned_message_id.is_valid())) {
VLOG(notifications) << "Delay new message notification for " << m->message_id << " in " << d->dialog_id << " with "
<< pending_notifications.size() << " already waiting messages";
if (pending_notifications.empty()) {
VLOG(notifications) << "Create FlushPendingNewMessageNotificationsSleepActor for " << d->dialog_id;
create_actor<SleepActor>("FlushPendingNewMessageNotificationsSleepActor", 5.0,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id,
from_mentions](Result<Unit> result) {
VLOG(notifications)
<< "Pending notifications timeout in " << dialog_id << " has expired";
send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications,
dialog_id, from_mentions, DialogId());
}))
.release();
}
auto last_settings_dialog_id = (pending_notifications.empty() ? DialogId() : pending_notifications.back().first);
pending_notifications.emplace_back((have_settings ? DialogId() : settings_dialog_id), m->message_id);
if (!have_settings && last_settings_dialog_id != settings_dialog_id) {
VLOG(notifications) << "Fetch notification settings for " << settings_dialog_id;
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions,
settings_dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions,
settings_dialog_id);
});
if (settings_dialog == nullptr && have_input_peer(settings_dialog_id, AccessRights::Read)) {
force_create_dialog(settings_dialog_id, "add_new_message_notification 2");
settings_dialog = get_dialog(settings_dialog_id);
}
if (settings_dialog != nullptr) {
td_->notification_settings_manager_->send_get_dialog_notification_settings_query(
settings_dialog_id, MessageId() /* TODO */, std::move(promise));
} else {
send_get_dialog_query(settings_dialog_id, std::move(promise), 0, "add_new_message_notification");
}
}
if (missing_pinned_message_id.is_valid()) {
VLOG(notifications) << "Fetch pinned " << missing_pinned_message_id;
auto promise = PromiseCreator::lambda(
[actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions,
dialog_id);
});
get_message_from_server({d->dialog_id, missing_pinned_message_id}, std::move(promise),
"add_new_message_notification");
}
return false;
}
LOG_IF(WARNING, !have_settings) << "Have no notification settings for " << settings_dialog_id
<< ", but forced to send notification about " << m->message_id << " in "
<< d->dialog_id;
auto &group_info = get_notification_group_info(d, m);
auto notification_group_id = get_dialog_notification_group_id(d->dialog_id, group_info);
if (!notification_group_id.is_valid()) {
return false;
}
// if !force, then add_message_to_dialog will add the correspondence
m->notification_id = get_next_notification_id(d, notification_group_id, force ? m->message_id : MessageId());
if (!m->notification_id.is_valid()) {
return false;
}
bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id,
"add_new_message_notification 3");
CHECK(is_changed);
if (is_pinned) {
set_dialog_pinned_message_notification(d, from_mentions ? m->message_id : MessageId(),
"add_new_message_notification");
}
if (!m->notification_id.is_valid()) {
// protection from accidental notification_id removal in set_dialog_pinned_message_notification
return false;
}
VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " << group_info.group_id
<< '/' << d->dialog_id;
int32 min_delay_ms = 0;
if (need_delay_message_content_notification(m->content.get(), td_->contacts_manager_->get_my_id())) {
min_delay_ms = 3000; // 3 seconds
} else if (td_->is_online() && d->is_opened) {
min_delay_ms = 1000; // 1 second
}
auto ringtone_id = get_dialog_notification_ringtone_id(settings_dialog_id, settings_dialog);
bool is_silent = m->disable_notification || m->message_id <= d->max_notification_message_id;
send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id,
from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages, d->dialog_id,
m->date, settings_dialog_id, m->disable_notification, is_silent ? 0 : ringtone_id, min_delay_ms,
m->notification_id,
create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)),
"add_new_message_notification");
return true;
}
void MessagesManager::flush_pending_new_message_notifications(DialogId dialog_id, bool from_mentions,
DialogId settings_dialog_id) {
// flush pending notifications even while closing
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto &pending_notifications =
from_mentions ? d->pending_new_mention_notifications : d->pending_new_message_notifications;
if (pending_notifications.empty()) {
VLOG(notifications) << "Have no pending notifications in " << dialog_id << " to flush";
return;
}
for (auto &it : pending_notifications) {
if (it.first == settings_dialog_id || !settings_dialog_id.is_valid()) {
it.first = DialogId();
}
}
VLOG(notifications) << "Flush pending notifications in " << dialog_id
<< " because of received notification settings in " << settings_dialog_id;
auto it = pending_notifications.begin();
while (it != pending_notifications.end() && it->first == DialogId()) {
auto m = get_message(d, it->second);
if (m != nullptr && add_new_message_notification(d, m, true)) {
on_message_changed(d, m, false, "flush_pending_new_message_notifications");
}
++it;
}
if (it == pending_notifications.end()) {
reset_to_empty(pending_notifications);
} else {
pending_notifications.erase(pending_notifications.begin(), it);
}
}
void MessagesManager::remove_all_dialog_notifications(Dialog *d, bool from_mentions, const char *source) {
// removes up to group_info.last_notification_id
NotificationGroupInfo &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (group_info.group_id.is_valid() && group_info.last_notification_id.is_valid() &&
group_info.max_removed_notification_id != group_info.last_notification_id) {
VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << d->dialog_id << " to "
<< group_info.last_notification_id << " from " << source;
group_info.max_removed_notification_id = group_info.last_notification_id;
if (d->max_notification_message_id > group_info.max_removed_message_id) {
group_info.max_removed_message_id = d->max_notification_message_id.get_prev_server_message_id();
}
if (!d->pending_new_message_notifications.empty()) {
for (auto &it : d->pending_new_message_notifications) {
it.first = DialogId();
}
flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast<int64>(2))));
}
// remove_message_notifications will be called by NotificationManager
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group,
group_info.group_id, group_info.last_notification_id, MessageId(), 0, true, Promise<Unit>());
if (d->new_secret_chat_notification_id.is_valid() && &group_info == &d->message_notification_group) {
remove_new_secret_chat_notification(d, false);
} else {
bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), source);
CHECK(is_changed);
}
}
}
void MessagesManager::remove_message_dialog_notifications(Dialog *d, MessageId max_message_id, bool from_mentions,
const char *source) {
// removes up to max_message_id
CHECK(!max_message_id.is_scheduled());
NotificationGroupInfo &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (!group_info.group_id.is_valid()) {
return;
}
VLOG(notifications) << "Remove message dialog notifications in " << group_info.group_id << '/' << d->dialog_id
<< " up to " << max_message_id << " from " << source;
if (!d->pending_new_message_notifications.empty()) {
for (auto &it : d->pending_new_message_notifications) {
if (it.second <= max_message_id) {
it.first = DialogId();
}
}
flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast<int64>(3))));
}
auto max_notification_message_id = max_message_id;
if (d->last_message_id.is_valid() && max_notification_message_id >= d->last_message_id) {
max_notification_message_id = d->last_message_id;
set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(),
"remove_message_dialog_notifications 1");
} else if (max_notification_message_id == MessageId::max()) {
max_notification_message_id = get_next_local_message_id(d);
set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(),
"remove_message_dialog_notifications 2");
} else {
LOG(FATAL) << "TODO support notification deletion up to " << max_message_id << " if it would be ever needed";
}
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, group_info.group_id,
NotificationId(), max_notification_message_id, 0, true, Promise<Unit>());
}
void MessagesManager::send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const {
CHECK(m != nullptr);
CHECK(d->is_update_new_chat_sent);
if (!td_->auth_manager_->is_bot()) {
d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
}
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageSendSucceeded>(
get_message_object(d->dialog_id, m, "send_update_message_send_succeeded"), old_message_id.get()));
}
void MessagesManager::send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog,
const char *source) {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (is_message_in_dialog) {
delete_bot_command_message_id(d->dialog_id, m->message_id);
try_add_bot_command_message_id(d->dialog_id, m);
reregister_message_reply(d->dialog_id, m);
update_message_max_reply_media_timestamp(d, m, false); // because the message reply can be just registered
update_message_max_own_media_timestamp(d, m);
}
send_update_message_content_impl(d->dialog_id, m, source);
}
void MessagesManager::send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const {
CHECK(m != nullptr);
if (!m->is_update_sent) {
LOG(INFO) << "Skip updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source;
return;
}
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),
get_message_max_media_timestamp(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) {
CHECK(m != nullptr);
cancel_dialog_action(dialog_id, m);
auto edit_date = m->hide_edit_date ? 0 : m->edit_date;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageEdited>(
dialog_id.get(), m->message_id.get(), edit_date,
get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup)));
}
void MessagesManager::send_update_message_interaction_info(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || !m->is_update_sent) {
return;
}
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageInteractionInfo>(dialog_id.get(), m->message_id.get(),
get_message_interaction_info_object(dialog_id, m)));
}
void MessagesManager::send_update_message_unread_reactions(DialogId dialog_id, const Message *m,
int32 unread_reaction_count) const {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot()) {
return;
}
if (!m->is_update_sent) {
LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << unread_reaction_count;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatUnreadReactionCount>(dialog_id.get(), unread_reaction_count));
return;
}
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageUnreadReactions>(
dialog_id.get(), m->message_id.get(), get_unread_reactions_object(dialog_id, m), unread_reaction_count));
}
void MessagesManager::send_update_message_live_location_viewed(FullMessageId full_message_id) {
CHECK(get_message(full_message_id) != nullptr);
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateMessageLiveLocationViewed>(full_message_id.get_dialog_id().get(),
full_message_id.get_message_id().get()));
}
void MessagesManager::send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids,
bool is_permanent) const {
if (message_ids.empty()) {
return;
}
LOG_CHECK(have_dialog(dialog_id)) << "Wrong " << dialog_id << " in send_update_delete_messages";
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateDeleteMessages>(dialog_id.get(), std::move(message_ids), is_permanent, false));
}
void MessagesManager::send_update_new_chat(Dialog *d) {
CHECK(d != nullptr);
CHECK(d->messages == nullptr);
if ((d->dialog_id.get_type() == DialogType::User || d->dialog_id.get_type() == DialogType::SecretChat) &&
td_->auth_manager_->is_bot()) {
(void)get_dialog_photo(d->dialog_id); // to apply pending user photo
}
d->is_update_new_chat_being_sent = true;
auto chat_object = get_chat_object(d);
bool has_action_bar = chat_object->action_bar_ != nullptr;
bool has_theme = !chat_object->theme_name_.empty();
d->last_sent_has_scheduled_messages = chat_object->has_scheduled_messages_;
send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateNewChat>(std::move(chat_object)));
d->is_update_new_chat_sent = true;
d->is_update_new_chat_being_sent = false;
if (has_action_bar) {
send_update_secret_chats_with_user_action_bar(d);
}
if (has_theme) {
send_update_secret_chats_with_user_theme(d);
}
}
void MessagesManager::send_update_chat_draft_message(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_draft_message";
on_dialog_updated(d->dialog_id, "send_update_chat_draft_message");
if (d->draft_message == nullptr || can_send_message(d->dialog_id).is_ok()) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatDraftMessage>(
d->dialog_id.get(), get_draft_message_object(d->draft_message), get_chat_positions_object(d)));
}
}
void MessagesManager::send_update_chat_last_message(Dialog *d, const char *source) {
update_dialog_pos(d, source, false);
send_update_chat_last_message_impl(d, source);
}
void MessagesManager::send_update_chat_last_message_impl(const Dialog *d, const char *source) const {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_last_message from "
<< source;
LOG(INFO) << "Send updateChatLastMessage in " << d->dialog_id << " to " << d->last_message_id << " from " << source;
const auto *m = get_message(d, d->last_message_id);
auto message_object = get_message_object(d->dialog_id, m, "send_update_chat_last_message_impl");
auto positions_object = get_chat_positions_object(d);
auto update = td_api::make_object<td_api::updateChatLastMessage>(d->dialog_id.get(), std::move(message_object),
std::move(positions_object));
send_closure(G()->td(), &Td::send_update, std::move(update));
}
void MessagesManager::send_update_chat_filters() {
if (td_->auth_manager_->is_bot()) {
return;
}
is_update_chat_filters_sent_ = true;
send_closure(G()->td(), &Td::send_update, get_update_chat_filters_object());
}
void MessagesManager::save_dialog_filters() {
if (td_->auth_manager_->is_bot()) {
return;
}
DialogFiltersLogEvent log_event;
log_event.server_main_dialog_list_position = server_main_dialog_list_position_;
log_event.main_dialog_list_position = main_dialog_list_position_;
log_event.updated_date = dialog_filters_updated_date_;
log_event.server_dialog_filters_in = &server_dialog_filters_;
log_event.dialog_filters_in = &dialog_filters_;
LOG(INFO) << "Save server chat filters "
<< get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
<< " and local chat filters " << get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
G()->td_db()->get_binlog_pmc()->set("dialog_filters", log_event_store(log_event).as_slice().str());
}
void MessagesManager::send_update_unread_message_count(DialogList &list, DialogId dialog_id, bool force,
const char *source, bool from_database) {
if (td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
return;
}
auto dialog_list_id = list.dialog_list_id;
CHECK(list.is_message_unread_count_inited_);
if (list.unread_message_muted_count_ < 0 || list.unread_message_muted_count_ > list.unread_message_total_count_) {
LOG(ERROR) << "Unread message count became invalid in " << dialog_list_id << ": "
<< list.unread_message_total_count_ << '/'
<< list.unread_message_total_count_ - list.unread_message_muted_count_ << " from " << source << " and "
<< dialog_id;
if (list.unread_message_muted_count_ < 0) {
list.unread_message_muted_count_ = 0;
}
if (list.unread_message_muted_count_ > list.unread_message_total_count_) {
list.unread_message_total_count_ = list.unread_message_muted_count_;
}
}
if (!from_database) {
LOG(INFO) << "Save unread message count in " << dialog_list_id;
G()->td_db()->get_binlog_pmc()->set(
PSTRING() << "unread_message_count" << dialog_list_id.get(),
PSTRING() << list.unread_message_total_count_ << ' ' << list.unread_message_muted_count_);
}
int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_;
if (!force && running_get_difference_) {
LOG(INFO) << "Postpone updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_
<< '/' << unread_unmuted_count << " from " << source << " and " << dialog_id;
postponed_unread_message_count_updates_.insert(dialog_list_id);
} else {
postponed_unread_message_count_updates_.erase(dialog_list_id);
LOG(INFO) << "Send updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_
<< '/' << unread_unmuted_count << " from " << source << " and " << dialog_id;
send_closure(G()->td(), &Td::send_update, get_update_unread_message_count_object(list));
}
}
void MessagesManager::send_update_unread_chat_count(DialogList &list, DialogId dialog_id, bool force,
const char *source, bool from_database) {
if (td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
return;
}
auto dialog_list_id = list.dialog_list_id;
CHECK(list.is_dialog_unread_count_inited_);
if (list.unread_dialog_muted_marked_count_ < 0 ||
list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_ ||
list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_ ||
list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ <
list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) {
LOG(ERROR) << "Unread chat count became invalid in " << dialog_list_id << ": " << list.unread_dialog_total_count_
<< '/' << list.unread_dialog_total_count_ - list.unread_dialog_muted_count_ << '/'
<< list.unread_dialog_marked_count_ << '/'
<< list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_ << " from " << source
<< " and " << dialog_id;
if (list.unread_dialog_muted_marked_count_ < 0) {
list.unread_dialog_muted_marked_count_ = 0;
}
if (list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_) {
list.unread_dialog_marked_count_ = list.unread_dialog_muted_marked_count_;
}
if (list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_) {
list.unread_dialog_muted_count_ = list.unread_dialog_muted_marked_count_;
}
if (list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ <
list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) {
list.unread_dialog_total_count_ =
list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_;
}
}
if (!from_database) {
save_unread_chat_count(list);
}
bool need_postpone = !force && running_get_difference_;
int32 unread_unmuted_count = list.unread_dialog_total_count_ - list.unread_dialog_muted_count_;
int32 unread_unmuted_marked_count = list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_;
LOG(INFO) << (need_postpone ? "Postpone" : "Send") << " updateUnreadChatCount in " << dialog_list_id << " to "
<< list.in_memory_dialog_total_count_ << '/' << list.server_dialog_total_count_ << '+'
<< list.secret_chat_total_count_ << '/' << list.unread_dialog_total_count_ << '/' << unread_unmuted_count
<< '/' << list.unread_dialog_marked_count_ << '/' << unread_unmuted_marked_count << " from " << source
<< " and " << dialog_id;
if (need_postpone) {
postponed_unread_chat_count_updates_.insert(dialog_list_id);
} else {
postponed_unread_chat_count_updates_.erase(dialog_list_id);
send_closure(G()->td(), &Td::send_update, get_update_unread_chat_count_object(list));
}
}
void MessagesManager::save_unread_chat_count(const DialogList &list) {
LOG(INFO) << "Save unread chat count in " << list.dialog_list_id;
G()->td_db()->get_binlog_pmc()->set(
PSTRING() << "unread_dialog_count" << list.dialog_list_id.get(),
PSTRING() << list.unread_dialog_total_count_ << ' ' << list.unread_dialog_muted_count_ << ' '
<< list.unread_dialog_marked_count_ << ' ' << list.unread_dialog_muted_marked_count_ << ' '
<< list.server_dialog_total_count_ << ' ' << list.secret_chat_total_count_);
}
void MessagesManager::send_update_chat_read_inbox(const Dialog *d, bool force, const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_inbox from "
<< source;
on_dialog_updated(d->dialog_id, source);
if (!force && (running_get_difference_ || running_get_channel_difference(d->dialog_id) ||
get_channel_difference_to_log_event_id_.count(d->dialog_id) != 0)) {
LOG(INFO) << "Postpone updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to "
<< d->server_unread_count << " + " << d->local_unread_count << " from " << source;
postponed_chat_read_inbox_updates_.insert(d->dialog_id);
} else {
postponed_chat_read_inbox_updates_.erase(d->dialog_id);
LOG(INFO) << "Send updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to "
<< d->server_unread_count << " + " << d->local_unread_count << " from " << source;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatReadInbox>(d->dialog_id.get(), d->last_read_inbox_message_id.get(),
d->server_unread_count + d->local_unread_count));
}
}
void MessagesManager::send_update_chat_read_outbox(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_outbox";
on_dialog_updated(d->dialog_id, "send_update_chat_read_outbox");
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatReadOutbox>(d->dialog_id.get(), d->last_read_outbox_message_id.get()));
}
void MessagesManager::send_update_chat_unread_mention_count(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_unread_mention_count";
LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count;
on_dialog_updated(d->dialog_id, "send_update_chat_unread_mention_count");
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatUnreadMentionCount>(d->dialog_id.get(), d->unread_mention_count));
}
void MessagesManager::send_update_chat_unread_reaction_count(const Dialog *d, const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id
<< " in send_update_chat_unread_reaction_count from " << source;
LOG(INFO) << "Update unread reaction message count in " << d->dialog_id << " to " << d->unread_reaction_count
<< " from " << source;
on_dialog_updated(d->dialog_id, "send_update_chat_unread_reaction_count");
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatUnreadReactionCount>(d->dialog_id.get(), d->unread_reaction_count));
}
void MessagesManager::send_update_chat_position(DialogListId dialog_list_id, const Dialog *d,
const char *source) const {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_position";
LOG(INFO) << "Send updateChatPosition for " << d->dialog_id << " in " << dialog_list_id << " from " << source;
auto position = get_chat_position_object(dialog_list_id, d);
if (position == nullptr) {
position = td_api::make_object<td_api::chatPosition>(dialog_list_id.get_chat_list_object(), 0, false, nullptr);
}
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatPosition>(d->dialog_id.get(), std::move(position)));
}
void MessagesManager::send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const {
if (td_->auth_manager_->is_bot()) {
return;
}
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatOnlineMemberCount>(dialog_id.get(), online_member_count));
}
void MessagesManager::send_update_secret_chats_with_user_action_bar(const Dialog *d) const {
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->dialog_id.get_type() != DialogType::User) {
return;
}
td_->contacts_manager_->for_each_secret_chat_with_user(
d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) {
DialogId dialog_id(secret_chat_id);
auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog
if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) {
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatActionBar>(dialog_id.get(), get_chat_action_bar_object(user_d)));
}
});
}
void MessagesManager::send_update_chat_action_bar(Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->action_bar != nullptr && d->action_bar->is_empty()) {
d->action_bar = nullptr;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_action_bar";
on_dialog_updated(d->dialog_id, "send_update_chat_action_bar");
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatActionBar>(d->dialog_id.get(), get_chat_action_bar_object(d)));
send_update_secret_chats_with_user_action_bar(d);
}
void MessagesManager::send_update_chat_available_reactions(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_available_reactions";
auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object();
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatAvailableReactions>(d->dialog_id.get(), std::move(available_reactions)));
}
void MessagesManager::send_update_secret_chats_with_user_theme(const Dialog *d) const {
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->dialog_id.get_type() != DialogType::User) {
return;
}
td_->contacts_manager_->for_each_secret_chat_with_user(
d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) {
DialogId dialog_id(secret_chat_id);
auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog
if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) {
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatTheme>(dialog_id.get(), user_d->theme_name));
}
});
}
void MessagesManager::send_update_chat_theme(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_theme";
on_dialog_updated(d->dialog_id, "send_update_chat_theme");
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatTheme>(d->dialog_id.get(), d->theme_name));
send_update_secret_chats_with_user_theme(d);
}
void MessagesManager::send_update_chat_pending_join_requests(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_pending_join_requests";
on_dialog_updated(d->dialog_id, "send_update_chat_pending_join_requests");
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatPendingJoinRequests>(d->dialog_id.get(),
get_chat_join_requests_info_object(d)));
}
void MessagesManager::send_update_chat_video_chat(const Dialog *d) {
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_video_chat";
on_dialog_updated(d->dialog_id, "send_update_chat_video_chat");
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatVideoChat>(d->dialog_id.get(), get_video_chat_object(d)));
}
void MessagesManager::send_update_chat_message_sender(const Dialog *d) {
CHECK(!td_->auth_manager_->is_bot());
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_sender";
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatMessageSender>(d->dialog_id.get(), get_default_message_sender_object(d)));
}
void MessagesManager::send_update_chat_message_ttl(const Dialog *d) {
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_ttl";
on_dialog_updated(d->dialog_id, "send_update_chat_message_ttl");
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatMessageTtl>(d->dialog_id.get(), d->message_ttl.get_message_ttl_object()));
}
void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->scheduled_messages == nullptr) {
if (d->has_scheduled_database_messages) {
if (d->has_loaded_scheduled_messages_from_database) {
set_dialog_has_scheduled_database_messages_impl(d, false);
} else {
CHECK(G()->parameters().use_message_db);
repair_dialog_scheduled_messages(d);
}
}
if (d->has_scheduled_server_messages) {
if (from_deletion && d->scheduled_messages_sync_generation > 0) {
set_dialog_has_scheduled_server_messages(d, false);
} else {
d->last_repair_scheduled_messages_generation = 0;
repair_dialog_scheduled_messages(d);
}
}
}
LOG(INFO) << "In " << d->dialog_id << " have scheduled messages on server = " << d->has_scheduled_server_messages
<< ", in database = " << d->has_scheduled_database_messages
<< " and in memory = " << (d->scheduled_messages != nullptr)
<< "; was loaded from database = " << d->has_loaded_scheduled_messages_from_database;
bool has_scheduled_messages = get_dialog_has_scheduled_messages(d);
if (has_scheduled_messages == d->last_sent_has_scheduled_messages) {
return;
}
d->last_sent_has_scheduled_messages = has_scheduled_messages;
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_has_scheduled_messages";
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatHasScheduledMessages>(d->dialog_id.get(), has_scheduled_messages));
}
void MessagesManager::send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id,
DialogId typing_dialog_id, const DialogAction &action) {
if (td_->auth_manager_->is_bot()) {
return;
}
LOG(DEBUG) << "Send " << action << " of " << typing_dialog_id << " in thread of " << top_thread_message_id << " in "
<< dialog_id;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatAction>(
dialog_id.get(), top_thread_message_id.get(),
get_message_sender_object(td_, typing_dialog_id, "send_update_chat_action"),
action.get_chat_action_object()));
}
void MessagesManager::on_send_message_get_quick_ack(int64 random_id) {
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
LOG(ERROR) << "Receive quick ack about unknown message with random_id = " << random_id;
return;
}
auto dialog_id = it->second.get_dialog_id();
auto message_id = it->second.get_message_id();
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageSendAcknowledged>(dialog_id.get(), message_id.get()));
}
void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog_id,
const telegram_api::Updates *updates_ptr, const char *source) {
CHECK(updates_ptr != nullptr);
CHECK(source != nullptr);
auto sent_messages = UpdatesManager::get_new_messages(updates_ptr);
auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates_ptr);
auto is_invalid_poll_message = [](const telegram_api::Message *message) {
CHECK(message != nullptr);
auto constructor_id = message->get_id();
if (constructor_id == telegram_api::messageEmpty::ID) {
return true;
}
if (constructor_id != telegram_api::message::ID) {
return false;
}
auto media = static_cast<const telegram_api::message *>(message)->media_.get();
if (media == nullptr || media->get_id() != telegram_api::messageMediaPoll::ID) {
return false;
}
auto poll = static_cast<const telegram_api::messageMediaPoll *>(media)->poll_.get();
return !PollId(poll->id_).is_valid();
};
if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u ||
*sent_messages_random_ids.begin() != random_id ||
DialogId::get_message_dialog_id(sent_messages[0].first) != dialog_id ||
is_invalid_poll_message(sent_messages[0].first)) {
LOG(ERROR) << "Receive wrong result for sending message with random_id " << random_id << " from " << source
<< " to " << dialog_id << ": " << oneline(to_string(*updates_ptr));
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (dialog_id.get_type() == DialogType::Channel) {
get_channel_difference(dialog_id, d->pts, true, "check_send_message_result");
} else {
td_->updates_manager_->schedule_get_difference("check_send_message_result");
}
repair_dialog_scheduled_messages(d);
}
}
void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id,
bool have_new_message, const char *source) {
LOG(INFO) << "Update replies of " << FullMessageId{dialog_id, old_message_id} << " to " << new_message_id << " from "
<< source;
auto it = replied_yet_unsent_messages_.find({dialog_id, old_message_id});
if (it == replied_yet_unsent_messages_.end()) {
return;
}
CHECK(old_message_id.is_yet_unsent());
for (auto message_id : it->second) {
CHECK(message_id.is_yet_unsent());
FullMessageId full_message_id{dialog_id, message_id};
auto replied_m = get_message(full_message_id);
CHECK(replied_m != nullptr);
CHECK(replied_m->reply_to_message_id == old_message_id);
LOG(INFO) << "Update replied message in " << full_message_id << " from " << old_message_id << " to "
<< new_message_id;
unregister_message_reply(dialog_id, replied_m);
replied_m->reply_to_message_id = new_message_id;
// TODO rewrite send message log event
register_message_reply(dialog_id, replied_m);
}
if (have_new_message) {
CHECK(!new_message_id.is_yet_unsent());
replied_by_yet_unsent_messages_[FullMessageId{dialog_id, new_message_id}] = static_cast<int32>(it->second.size());
} else {
replied_by_yet_unsent_messages_.erase(FullMessageId{dialog_id, new_message_id});
}
replied_yet_unsent_messages_.erase(it);
}
FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageId new_message_id, int32 date,
int32 ttl_period, FileId new_file_id, const char *source) {
CHECK(source != nullptr);
// do not try to run getDifference from this function
if (DROP_SEND_MESSAGE_UPDATES) {
return {};
}
if (!new_message_id.is_valid()) {
LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
on_send_message_fail(
random_id,
Status::Error(500, "Internal Server Error: receive invalid message identifier as sent message identifier"));
return {};
}
if (new_message_id.is_yet_unsent()) {
LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
on_send_message_fail(random_id,
Status::Error(500, "Internal Server Error: receive yet unsent message as sent message"));
return {};
}
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
LOG(ERROR) << "Result from sendMessage for " << new_message_id << " with random_id " << random_id << " sent at "
<< date << " comes from " << source << " after updateNewMessageId, but was not discarded by pts";
return {};
}
auto dialog_id = it->second.get_dialog_id();
auto old_message_id = it->second.get_message_id();
if (new_message_id.is_local() && dialog_id.get_type() != DialogType::SecretChat) {
LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
on_send_message_fail(random_id, Status::Error(500, "Internal Server Error: receive local as sent message"));
return {};
}
being_sent_messages_.erase(it);
// must be called before delete_message
update_reply_to_message_id(dialog_id, old_message_id, new_message_id, true, "on_send_message_success");
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
bool need_update_dialog_pos = false;
being_readded_message_id_ = {dialog_id, old_message_id};
unique_ptr<Message> sent_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, source);
if (sent_message == nullptr) {
delete_sent_message_on_server(dialog_id, new_message_id, old_message_id);
being_readded_message_id_ = FullMessageId();
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << old_message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(d, 5);
}
// imitation of update_message(d, sent_message.get(), std::move(new_message), &need_update_dialog_pos, false);
if (date <= 0) {
LOG(ERROR) << "Receive " << new_message_id << " in " << dialog_id << " with wrong date " << date << " from "
<< source;
} else {
LOG_CHECK(sent_message->date > 0) << old_message_id << ' ' << sent_message->message_id << ' ' << new_message_id
<< ' ' << sent_message->date << ' ' << date << ' ' << source;
sent_message->date = date;
CHECK(d->last_message_id != old_message_id);
}
sent_message->ttl_period = ttl_period;
if (merge_message_content_file_id(td_, sent_message->content.get(), new_file_id)) {
send_update_message_content(d, sent_message.get(), false, source);
}
if (old_message_id.is_valid() && new_message_id < old_message_id && !has_qts_messages(dialog_id) &&
!d->had_yet_unsent_message_id_overflow) {
LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << new_message_id;
}
set_message_id(sent_message, new_message_id);
sent_message->from_database = false;
sent_message->have_previous = true;
sent_message->have_next = true;
if (sent_message->reply_to_message_id != MessageId() && sent_message->reply_to_message_id.is_yet_unsent()) {
LOG(INFO) << "Drop reply to " << sent_message->reply_to_message_id;
sent_message->reply_to_message_id = MessageId();
}
send_update_message_send_succeeded(d, old_message_id, sent_message.get());
bool need_update = true;
Message *m = add_message_to_dialog(d, std::move(sent_message), true, &need_update, &need_update_dialog_pos, source);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, source);
}
if (m == nullptr) {
if (!(old_message_id.is_valid() && new_message_id < old_message_id) &&
!(ttl_period > 0 && date + ttl_period <= G()->server_time())) {
// if message ID has decreased, which could happen if some messages were lost,
// or the message has already been deleted after TTL period, then the error is expected
LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << new_message_id
<< " from " << source << ": " << debug_add_message_to_dialog_fail_reason_;
}
send_update_delete_messages(dialog_id, {new_message_id.get()}, true);
being_readded_message_id_ = FullMessageId();
return {};
}
try_add_active_live_location(dialog_id, m);
update_reply_count_by_message(d, +1, m);
update_forward_count(dialog_id, m);
being_readded_message_id_ = FullMessageId();
return {dialog_id, new_message_id};
}
void MessagesManager::on_send_message_file_part_missing(int64 random_id, int bad_part) {
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
LOG(WARNING) << "Receive FILE_PART_" << bad_part
<< "_MISSING about successfully sent message with random_id = " << random_id;
return;
}
auto full_message_id = it->second;
being_sent_messages_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
return;
}
auto dialog_id = full_message_id.get_dialog_id();
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(get_dialog(dialog_id), 5);
}
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(!m->message_id.is_scheduled());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
// need to change message random_id before resending
m->random_id = generate_new_random_id(d);
add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
auto log_event = SendMessageLogEvent(dialog_id, m);
CHECK(m->send_message_log_event_id != 0);
binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage,
get_log_event_storer(log_event));
}
do_send_message(dialog_id, m, {bad_part});
}
void MessagesManager::on_send_message_file_reference_error(int64 random_id) {
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
LOG(WARNING) << "Receive file reference invalid error about successfully sent message with random_id = "
<< random_id;
return;
}
auto full_message_id = it->second;
being_sent_messages_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
return;
}
auto dialog_id = full_message_id.get_dialog_id();
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(get_dialog(dialog_id), 5);
}
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(!m->message_id.is_scheduled());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
// need to change message random_id before resending
m->random_id = generate_new_random_id(d);
add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
auto log_event = SendMessageLogEvent(dialog_id, m);
CHECK(m->send_message_log_event_id != 0);
binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage,
get_log_event_storer(log_event));
}
do_send_message(dialog_id, m, {-1});
}
void MessagesManager::on_send_media_group_file_reference_error(DialogId dialog_id, vector<int64> random_ids) {
int64 media_album_id = 0;
vector<MessageId> message_ids;
vector<Message *> messages;
for (auto &random_id : random_ids) {
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
LOG(ERROR) << "Receive file reference invalid error about successfully sent message with random_id = "
<< random_id;
continue;
}
auto full_message_id = it->second;
being_sent_messages_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
continue;
}
CHECK(m->media_album_id != 0);
CHECK(media_album_id == 0 || media_album_id == m->media_album_id);
media_album_id = m->media_album_id;
CHECK(dialog_id == full_message_id.get_dialog_id());
message_ids.push_back(m->message_id);
messages.push_back(m);
}
CHECK(dialog_id.get_type() != DialogType::SecretChat);
if (message_ids.empty()) {
// all messages was deleted, nothing to do
return;
}
auto &request = pending_message_group_sends_[media_album_id];
CHECK(!request.dialog_id.is_valid());
CHECK(request.finished_count == 0);
CHECK(request.results.empty());
request.dialog_id = dialog_id;
request.message_ids = std::move(message_ids);
request.is_finished.resize(request.message_ids.size(), false);
for (size_t i = 0; i < request.message_ids.size(); i++) {
request.results.push_back(Status::OK());
}
for (auto m : messages) {
do_send_message(dialog_id, m, {-1});
}
}
void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
CHECK(error.is_error());
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
if (error.code() != NetQuery::Canceled) {
LOG(ERROR) << "Receive error " << error << " about successfully sent message with random_id = " << random_id;
}
return;
}
auto full_message_id = it->second;
being_sent_messages_.erase(it);
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
return;
}
LOG_IF(ERROR, error.code() == NetQuery::Canceled)
<< "Receive error " << error << " about sent message with random_id = " << random_id;
auto dialog_id = full_message_id.get_dialog_id();
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(get_dialog(dialog_id), 5);
}
int error_code = error.code();
string error_message = error.message().str();
switch (error_code) {
case 420:
error_code = 429;
LOG(ERROR) << "Receive error 420: " << error_message;
break;
case 429:
// nothing special, error description has already been changed
LOG_IF(ERROR, !begins_with(error_message, "Too Many Requests: retry after "))
<< "Wrong error message: " << error_message;
break;
case 400:
if (error.message() == "MESSAGE_TOO_LONG") {
error_message = "Message is too long";
} else if (error.message() == "MEDIA_CAPTION_TOO_LONG") {
error_message = "Message caption is too long";
} else if (error.message() == "INPUT_USER_DEACTIVATED") {
error_code = 403;
error_message = "User is deactivated";
} else if (error.message() == "USER_IS_BLOCKED") {
error_code = 403;
if (td_->auth_manager_->is_bot()) {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
error_message = "Bot was blocked by the user";
break;
case DialogType::Chat:
case DialogType::Channel:
error_message = "Bot was kicked from the chat";
break;
case DialogType::None:
default:
UNREACHABLE();
}
} else {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
error_message = "User was blocked by the other user";
break;
case DialogType::Chat:
case DialogType::Channel:
error_message = "User is not in the chat";
break;
case DialogType::None:
default:
UNREACHABLE();
}
}
// TODO add check to send_message
} else if (error.message() == "USER_IS_BOT") {
if (td_->auth_manager_->is_bot() &&
(dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) {
error_code = 403;
if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
error_message = "Bot can't send messages to bots";
} else {
error_message = "Bot can't send messages to the user";
}
// TODO move check to send_message
}
} else if (error.message() == "PEER_ID_INVALID") {
error_code = 403;
if (td_->auth_manager_->is_bot() &&
(dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) {
error_message = "Bot can't initiate conversation with a user";
}
} else if (error.message() == "WC_CONVERT_URL_INVALID" || error.message() == "EXTERNAL_URL_INVALID") {
error_message = "Wrong HTTP URL specified";
} else if (error.message() == "WEBPAGE_CURL_FAILED") {
error_message = "Failed to get HTTP URL content";
} else if (error.message() == "WEBPAGE_MEDIA_EMPTY") {
error_message = "Wrong type of the web page content";
} else if (error.message() == "CHAT_FORWARDS_RESTRICTED") {
error_message = "Message has protected content and can't be forwarded";
} else if (error.message() == "MEDIA_EMPTY") {
auto content_type = m->content->get_type();
if (content_type == MessageContentType::Game) {
error_message = "Wrong game short name specified";
} else if (content_type == MessageContentType::Invoice) {
error_message = "Wrong invoice information specified";
} else if (content_type == MessageContentType::Poll) {
error_message = "Wrong poll data specified";
} else if (content_type == MessageContentType::Contact) {
error_message = "Wrong phone number specified";
} else {
error_message = "Wrong file identifier/HTTP URL specified";
}
} else if (error.message() == "PHOTO_EXT_INVALID") {
error_message = "Photo has unsupported extension. Use one of .jpg, .jpeg, .gif, .png, .tif or .bmp";
}
break;
case 403:
if (error.message() == "MESSAGE_DELETE_FORBIDDEN") {
error_code = 400;
error_message = "Message can't be deleted";
} else if (error.message() == "CHAT_GUEST_SEND_FORBIDDEN") {
error_code = 400;
if (dialog_id.get_type() == DialogType::Channel) {
td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), Promise<Unit>());
}
} else if (error.message() != "CHANNEL_PUBLIC_GROUP_NA" && error.message() != "USER_IS_BLOCKED" &&
error.message() != "USER_BOT_INVALID" && error.message() != "USER_DELETED") {
error_code = 400;
}
break;
// TODO other codes
default:
break;
}
if (error.message() == "REPLY_MARKUP_INVALID") {
if (m->reply_markup == nullptr) {
LOG(ERROR) << "Receive " << error.message() << " for "
<< oneline(to_string(get_message_object(dialog_id, m, "on_send_message_fail")));
} else {
LOG(ERROR) << "Receive " << error.message() << " for " << full_message_id << " with keyboard "
<< *m->reply_markup;
}
}
if (error_code != 403 && !(error_code == 500 && G()->close_flag())) {
LOG(WARNING) << "Failed to send " << full_message_id << " with the error " << error;
}
if (error_code <= 0) {
error_code = 500;
}
fail_send_message(full_message_id, error_code, error_message);
}
MessageId MessagesManager::get_next_message_id(Dialog *d, MessageType type) {
CHECK(d != nullptr);
MessageId last_message_id =
std::max({d->last_message_id, d->last_new_message_id, d->last_database_message_id, d->last_assigned_message_id,
d->last_clear_history_message_id, d->deleted_last_message_id, d->max_unavailable_message_id,
d->max_added_message_id});
if (last_message_id < d->last_read_inbox_message_id &&
d->last_read_inbox_message_id < d->last_new_message_id.get_next_server_message_id()) {
last_message_id = d->last_read_inbox_message_id;
}
if (last_message_id < d->last_read_outbox_message_id &&
d->last_read_outbox_message_id < d->last_new_message_id.get_next_server_message_id()) {
last_message_id = d->last_read_outbox_message_id;
}
d->last_assigned_message_id = last_message_id.get_next_message_id(type);
if (d->last_assigned_message_id > MessageId::max()) {
LOG(FATAL) << "Force restart because of message_id overflow: " << d->last_assigned_message_id;
}
CHECK(d->last_assigned_message_id.is_valid());
if (d->last_assigned_message_id.get_prev_server_message_id() != last_message_id.get_prev_server_message_id()) {
d->had_yet_unsent_message_id_overflow = true;
}
return d->last_assigned_message_id;
}
MessageId MessagesManager::get_next_yet_unsent_message_id(Dialog *d) {
return get_next_message_id(d, MessageType::YetUnsent);
}
MessageId MessagesManager::get_next_local_message_id(Dialog *d) {
return get_next_message_id(d, MessageType::Local);
}
MessageId MessagesManager::get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date) {
CHECK(date > 0);
MessageId message_id(ScheduledServerMessageId(1), date);
auto it = MessagesConstIterator(d, MessageId(ScheduledServerMessageId(), date + 1, true));
if (*it != nullptr && (*it)->message_id > message_id) {
message_id = (*it)->message_id;
}
auto &last_assigned_message_id = d->last_assigned_scheduled_message_id[date];
if (last_assigned_message_id != MessageId() && last_assigned_message_id > message_id) {
message_id = last_assigned_message_id;
}
last_assigned_message_id = message_id.get_next_message_id(MessageType::YetUnsent);
return last_assigned_message_id;
}
void MessagesManager::fail_send_message(FullMessageId full_message_id, int error_code, const string &error_message) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
MessageId old_message_id = full_message_id.get_message_id();
CHECK(old_message_id.is_valid() || old_message_id.is_valid_scheduled());
CHECK(old_message_id.is_yet_unsent());
update_reply_to_message_id(dialog_id, old_message_id, MessageId(), false, "fail_send_message");
bool need_update_dialog_pos = false;
being_readded_message_id_ = full_message_id;
unique_ptr<Message> message = delete_message(d, old_message_id, false, &need_update_dialog_pos, "fail send message");
if (message == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send update to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
being_readded_message_id_ = FullMessageId();
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// LOG(ERROR) << "Found " << old_message_id << " in inaccessible " << dialog_id;
// dump_debug_message_op(d, 5);
}
MessageId new_message_id = old_message_id.get_next_message_id(MessageType::Local); // trying to keep message position
if (!old_message_id.is_scheduled()) {
if (get_message_force(d, new_message_id, "fail_send_message") != nullptr || is_deleted_message(d, new_message_id) ||
new_message_id <= d->last_clear_history_message_id) {
new_message_id = get_next_local_message_id(d);
} else if (new_message_id > d->last_assigned_message_id) {
d->last_assigned_message_id = new_message_id;
}
} else {
while (get_message_force(d, new_message_id, "fail_send_message") != nullptr ||
is_deleted_message(d, new_message_id)) {
new_message_id = new_message_id.get_next_message_id(MessageType::Local);
}
}
set_message_id(message, new_message_id);
if (old_message_id.is_scheduled()) {
CHECK(message->message_id.is_valid_scheduled());
} else {
CHECK(message->message_id.is_valid());
}
if (message->forward_info == nullptr && message->view_count == 1) {
message->view_count = 0;
}
message->is_failed_to_send = true;
message->send_error_code = error_code;
message->send_error_message = error_message;
message->try_resend_at = 0.0;
auto retry_after = Global::get_retry_after(error_code, error_message);
if (retry_after > 0) {
message->try_resend_at = Time::now() + retry_after;
}
update_failed_to_send_message_content(td_, message->content);
message->from_database = false;
message->have_previous = true;
message->have_next = true;
bool need_update = false;
Message *m = add_message_to_dialog(dialog_id, std::move(message), false, &need_update, &need_update_dialog_pos,
"fail_send_message");
LOG_CHECK(m != nullptr) << "Failed to add failed to send " << new_message_id << " to " << dialog_id << " due to "
<< debug_add_message_to_dialog_fail_reason_;
if (!m->message_id.is_scheduled()) {
// add_message_to_dialog will not update counts, because need_update == false
update_message_count_by_index(d, +1, m);
update_reply_count_by_message(d, +1, m); // no-op because the message isn't server
}
register_new_local_message_id(d, m);
LOG(INFO) << "Send updateMessageSendFailed for " << full_message_id;
if (!td_->auth_manager_->is_bot()) {
d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
}
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageSendFailed>(get_message_object(dialog_id, m, "fail_send_message"),
old_message_id.get(), error_code, error_message));
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "fail_send_message");
}
being_readded_message_id_ = FullMessageId();
}
void MessagesManager::fail_send_message(FullMessageId full_message_id, Status error) {
fail_send_message(full_message_id, error.code(), error.message().str());
}
void MessagesManager::fail_edit_message_media(FullMessageId full_message_id, Status &&error) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
MessageId message_id = full_message_id.get_message_id();
CHECK(message_id.is_any_server());
auto m = get_message(d, message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
return;
}
CHECK(m->edited_content != nullptr);
m->edit_promise.set_error(std::move(error));
cancel_edit_message_media(dialog_id, m, "Failed to edit message. MUST BE IGNORED");
}
void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
tl_object_ptr<telegram_api::DraftMessage> &&draft_message) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive update chat draft in invalid " << dialog_id;
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_draft_message");
if (d == nullptr) {
LOG(INFO) << "Ignore update chat draft in unknown " << dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message";
} else {
send_get_dialog_query(dialog_id, Auto(), 0, "on_update_dialog_draft_message");
}
return;
}
auto draft = get_draft_message(td_->contacts_manager_.get(), std::move(draft_message));
if (top_thread_message_id.is_valid()) {
// TODO update thread message draft
return;
}
update_dialog_draft_message(d, std::move(draft), true, true);
}
bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr<DraftMessage> &&draft_message, bool from_update,
bool need_update_dialog_pos) {
CHECK(d != nullptr);
if (from_update && d->is_opened && d->draft_message != nullptr) {
// send the update anyway, despite it shouldn't be applied client-side
// return false;
}
if (draft_message == nullptr) {
if (d->draft_message != nullptr) {
d->draft_message = nullptr;
if (need_update_dialog_pos) {
update_dialog_pos(d, "update_dialog_draft_message", false);
}
send_update_chat_draft_message(d);
return true;
}
} else {
if (d->draft_message != nullptr && d->draft_message->reply_to_message_id == draft_message->reply_to_message_id &&
d->draft_message->input_message_text == draft_message->input_message_text) {
if (d->draft_message->date < draft_message->date) {
d->draft_message->date = draft_message->date;
if (need_update_dialog_pos) {
update_dialog_pos(d, "update_dialog_draft_message 2", false);
}
send_update_chat_draft_message(d);
return true;
}
} else {
if (!from_update || d->draft_message == nullptr || d->draft_message->date <= draft_message->date) {
d->draft_message = std::move(draft_message);
if (need_update_dialog_pos) {
update_dialog_pos(d, "update_dialog_draft_message 3", false);
}
send_update_chat_draft_message(d);
return true;
}
}
}
return false;
}
void MessagesManager::on_update_dialog_is_pinned(FolderId folder_id, DialogId dialog_id, bool is_pinned) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive pin of invalid " << dialog_id;
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_is_pinned");
if (d == nullptr) {
LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with unknown " << dialog_id;
on_update_pinned_dialogs(folder_id);
return;
}
if (d->order == DEFAULT_ORDER) {
// the chat can't be pinned or is already unpinned
// don't change it's folder_id
LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with " << dialog_id;
return;
}
auto *list = get_dialog_list(DialogListId(folder_id));
CHECK(list != nullptr);
if (!list->are_pinned_dialogs_inited_) {
return;
}
set_dialog_folder_id(d, folder_id);
set_dialog_is_pinned(DialogListId(folder_id), d, is_pinned);
}
void MessagesManager::on_update_pinned_dialogs(FolderId folder_id) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
// TODO log event + delete_log_event_promise
auto *list = get_dialog_list(DialogListId(folder_id));
if (list == nullptr || !list->are_pinned_dialogs_inited_) {
return;
}
// preload all pinned dialogs
int32 limit = narrow_cast<int32>(list->pinned_dialogs_.size()) +
(folder_id == FolderId::main() && sponsored_dialog_id_.is_valid() ? 1 : 0);
get_dialogs_from_list(DialogListId(folder_id), limit, Auto());
reload_pinned_dialogs(DialogListId(folder_id), Auto());
}
void MessagesManager::on_update_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive marking as unread of invalid " << dialog_id;
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_is_marked_as_unread");
if (d == nullptr) {
// nothing to do
return;
}
if (is_marked_as_unread == d->is_marked_as_unread) {
return;
}
set_dialog_is_marked_as_unread(d, is_marked_as_unread);
}
void MessagesManager::set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
CHECK(d != nullptr);
CHECK(d->is_marked_as_unread != is_marked_as_unread);
d->is_marked_as_unread = is_marked_as_unread;
on_dialog_updated(d->dialog_id, "set_dialog_is_marked_as_unread");
LOG(INFO) << "Set " << d->dialog_id << " is marked as unread to " << is_marked_as_unread;
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_marked_as_unread";
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatIsMarkedAsUnread>(d->dialog_id.get(), is_marked_as_unread));
if (d->server_unread_count + d->local_unread_count == 0 && need_unread_counter(d->order)) {
int32 delta = d->is_marked_as_unread ? 1 : -1;
for (auto &list : get_dialog_lists(d)) {
if (list.is_dialog_unread_count_inited_) {
list.unread_dialog_total_count_ += delta;
list.unread_dialog_marked_count_ += delta;
if (is_dialog_muted(d)) {
list.unread_dialog_muted_count_ += delta;
list.unread_dialog_muted_marked_count_ += delta;
}
send_update_unread_chat_count(list, d->dialog_id, true, "set_dialog_is_marked_as_unread");
}
}
if (!dialog_filters_.empty()) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_is_marked_as_unread");
}
}
}
void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive pinned message in invalid " << dialog_id;
return;
}
if (dialog_id.get_type() == DialogType::User) {
td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked);
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_is_blocked");
if (d == nullptr) {
// nothing to do
return;
}
if (d->is_blocked == is_blocked) {
if (!d->is_is_blocked_inited) {
CHECK(is_blocked == false);
d->is_is_blocked_inited = true;
on_dialog_updated(dialog_id, "on_update_dialog_is_blocked");
}
return;
}
set_dialog_is_blocked(d, is_blocked);
}
void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked) {
CHECK(d != nullptr);
CHECK(d->is_blocked != is_blocked);
d->is_blocked = is_blocked;
d->is_is_blocked_inited = true;
on_dialog_updated(d->dialog_id, "set_dialog_is_blocked");
LOG(INFO) << "Set " << d->dialog_id << " is_blocked to " << is_blocked;
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_blocked";
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatIsBlocked>(d->dialog_id.get(), is_blocked));
if (d->dialog_id.get_type() == DialogType::User) {
td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked);
if (d->know_action_bar) {
if (is_blocked) {
if (d->action_bar != nullptr) {
d->action_bar = nullptr;
send_update_chat_action_bar(d);
}
} else {
repair_dialog_action_bar(d, "on_dialog_user_is_blocked_updated");
}
}
td_->contacts_manager_->for_each_secret_chat_with_user(
d->dialog_id.get_user_id(), [this, is_blocked](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->is_blocked != is_blocked) {
set_dialog_is_blocked(d, is_blocked);
}
});
}
}
void MessagesManager::on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId pinned_message_id) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive pinned message in invalid " << dialog_id;
return;
}
if (!pinned_message_id.is_valid() && pinned_message_id != MessageId()) {
LOG(ERROR) << "Receive as pinned message " << pinned_message_id;
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_last_pinned_message_id");
if (d == nullptr) {
// nothing to do
return;
}
set_dialog_last_pinned_message_id(d, pinned_message_id);
}
void MessagesManager::set_dialog_last_pinned_message_id(Dialog *d, MessageId pinned_message_id) {
CHECK(d != nullptr);
Message *m = get_message_force(d, pinned_message_id, "set_dialog_last_pinned_message_id");
if (m != nullptr && update_message_is_pinned(d, m, true, "set_dialog_last_pinned_message_id")) {
on_message_changed(d, m, true, "set_dialog_last_pinned_message_id");
}
if (d->is_last_pinned_message_id_inited && d->last_pinned_message_id == pinned_message_id) {
return;
}
d->last_pinned_message_id = pinned_message_id;
d->is_last_pinned_message_id_inited = true;
on_dialog_updated(d->dialog_id, "set_dialog_last_pinned_message_id");
LOG(INFO) << "Set " << d->dialog_id << " pinned message to " << pinned_message_id;
}
void MessagesManager::drop_dialog_last_pinned_message_id(Dialog *d) {
d->last_pinned_message_id = MessageId();
d->is_last_pinned_message_id_inited = false;
on_dialog_updated(d->dialog_id, "drop_dialog_last_pinned_message_id");
LOG(INFO) << "Drop " << d->dialog_id << " pinned message";
create_actor<SleepActor>(
"ReloadDialogFullInfoActor", 1.0,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::reload_dialog_info_full, dialog_id,
"drop_dialog_last_pinned_message_id");
}))
.release();
}
void MessagesManager::on_update_dialog_theme_name(DialogId dialog_id, string theme_name) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive theme in invalid " << dialog_id;
return;
}
if (td_->auth_manager_->is_bot()) {
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_theme_name");
if (d == nullptr) {
// nothing to do
return;
}
set_dialog_theme_name(d, std::move(theme_name));
}
void MessagesManager::set_dialog_theme_name(Dialog *d, string theme_name) {
CHECK(d != nullptr);
if (td_->auth_manager_->is_bot()) {
return;
}
bool is_changed = d->theme_name != theme_name;
if (!is_changed && d->is_theme_name_inited) {
return;
}
d->theme_name = std::move(theme_name);
d->is_theme_name_inited = true;
if (is_changed) {
LOG(INFO) << "Set " << d->dialog_id << " theme to \"" << d->theme_name << '"';
send_update_chat_theme(d);
} else {
on_dialog_updated(d->dialog_id, "set_dialog_theme_name");
}
}
void MessagesManager::drop_dialog_pending_join_requests(DialogId dialog_id) {
CHECK(dialog_id.is_valid());
if (td_->auth_manager_->is_bot()) {
return;
}
auto d = get_dialog(dialog_id); // called from update_chat/channel, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
set_dialog_pending_join_requests(d, 0, {});
}
}
void MessagesManager::on_update_dialog_pending_join_requests(DialogId dialog_id, int32 pending_join_request_count,
vector<int64> pending_requesters) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive pending join request count in invalid " << dialog_id;
return;
}
if (td_->auth_manager_->is_bot()) {
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_pending_join_request_count");
if (d == nullptr) {
// nothing to do
return;
}
set_dialog_pending_join_requests(d, pending_join_request_count, UserId::get_user_ids(pending_requesters));
}
void MessagesManager::fix_pending_join_requests(DialogId dialog_id, int32 &pending_join_request_count,
vector<UserId> &pending_join_request_user_ids) const {
td::remove_if(pending_join_request_user_ids, [](UserId user_id) { return !user_id.is_valid(); });
bool need_drop_pending_join_requests = [&] {
if (pending_join_request_count < 0) {
return true;
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
return true;
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_status(chat_id);
if (!status.can_manage_invite_links()) {
return true;
}
break;
}
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
auto status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (!status.can_manage_invite_links()) {
return true;
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
return false;
}();
if (need_drop_pending_join_requests) {
pending_join_request_count = 0;
pending_join_request_user_ids.clear();
} else if (static_cast<size_t>(pending_join_request_count) < pending_join_request_user_ids.size()) {
LOG(ERROR) << "Fix pending join request count from " << pending_join_request_count << " to "
<< pending_join_request_user_ids.size();
pending_join_request_count = narrow_cast<int32>(pending_join_request_user_ids.size());
}
static constexpr size_t MAX_PENDING_JOIN_REQUESTS = 3;
if (pending_join_request_user_ids.size() > MAX_PENDING_JOIN_REQUESTS) {
pending_join_request_user_ids.resize(MAX_PENDING_JOIN_REQUESTS);
}
}
void MessagesManager::set_dialog_pending_join_requests(Dialog *d, int32 pending_join_request_count,
vector<UserId> pending_join_request_user_ids) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
fix_pending_join_requests(d->dialog_id, pending_join_request_count, pending_join_request_user_ids);
if (d->pending_join_request_count == pending_join_request_count &&
d->pending_join_request_user_ids == pending_join_request_user_ids) {
return;
}
d->pending_join_request_count = pending_join_request_count;
d->pending_join_request_user_ids = std::move(pending_join_request_user_ids);
send_update_chat_pending_join_requests(d);
}
void MessagesManager::repair_dialog_scheduled_messages(Dialog *d) {
if (td_->auth_manager_->is_bot() || d->dialog_id.get_type() == DialogType::SecretChat) {
return;
}
if (d->last_repair_scheduled_messages_generation == scheduled_messages_sync_generation_) {
return;
}
d->last_repair_scheduled_messages_generation = scheduled_messages_sync_generation_;
// TODO create log event
auto dialog_id = d->dialog_id;
LOG(INFO) << "Repair scheduled messages in " << dialog_id << " with generation "
<< d->last_repair_scheduled_messages_generation;
get_dialog_scheduled_messages(dialog_id, false, true,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Unit) {
send_closure(G()->messages_manager(), &MessagesManager::get_dialog_scheduled_messages,
dialog_id, true, true, Promise<Unit>());
}));
}
void MessagesManager::on_update_dialog_has_scheduled_server_messages(DialogId dialog_id,
bool has_scheduled_server_messages) {
CHECK(dialog_id.is_valid());
if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_has_scheduled_server_messages");
if (d == nullptr) {
// nothing to do
return;
}
LOG(INFO) << "Receive has_scheduled_server_messages = " << has_scheduled_server_messages << " in " << dialog_id;
if (d->has_scheduled_server_messages != has_scheduled_server_messages) {
set_dialog_has_scheduled_server_messages(d, has_scheduled_server_messages);
} else if (has_scheduled_server_messages !=
(d->has_scheduled_database_messages || d->scheduled_messages != nullptr)) {
repair_dialog_scheduled_messages(d);
}
}
void MessagesManager::set_dialog_has_scheduled_server_messages(Dialog *d, bool has_scheduled_server_messages) {
CHECK(d != nullptr);
CHECK(d->has_scheduled_server_messages != has_scheduled_server_messages);
d->has_scheduled_server_messages = has_scheduled_server_messages;
repair_dialog_scheduled_messages(d);
on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_server_messages");
LOG(INFO) << "Set " << d->dialog_id << " has_scheduled_server_messages to " << has_scheduled_server_messages;
send_update_chat_has_scheduled_messages(d, false);
}
void MessagesManager::set_dialog_has_scheduled_database_messages(DialogId dialog_id,
bool has_scheduled_database_messages) {
if (G()->close_flag()) {
return;
}
return set_dialog_has_scheduled_database_messages_impl(get_dialog(dialog_id), has_scheduled_database_messages);
}
void MessagesManager::set_dialog_has_scheduled_database_messages_impl(Dialog *d, bool has_scheduled_database_messages) {
CHECK(d != nullptr);
if (d->has_scheduled_database_messages == has_scheduled_database_messages) {
return;
}
if (d->has_scheduled_database_messages && d->scheduled_messages != nullptr &&
!d->scheduled_messages->message_id.is_yet_unsent()) {
// to prevent race between add_message_to_database and check of has_scheduled_database_messages
return;
}
CHECK(G()->parameters().use_message_db);
d->has_scheduled_database_messages = has_scheduled_database_messages;
on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_database_messages");
}
void MessagesManager::on_update_dialog_folder_id(DialogId dialog_id, FolderId folder_id) {
auto d = get_dialog_force(dialog_id, "on_update_dialog_folder_id");
if (d == nullptr) {
// nothing to do
return;
}
set_dialog_folder_id(d, folder_id);
}
void MessagesManager::set_dialog_folder_id(Dialog *d, FolderId folder_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
if (d->folder_id == folder_id) {
if (!d->is_folder_id_inited) {
LOG(INFO) << "Folder of " << d->dialog_id << " is still " << folder_id;
do_set_dialog_folder_id(d, folder_id);
}
return;
}
LOG(INFO) << "Change " << d->dialog_id << " folder from " << d->folder_id << " to " << folder_id;
auto dialog_positions = get_dialog_positions(d);
if (get_dialog_pinned_order(DialogListId(d->folder_id), d->dialog_id) != DEFAULT_ORDER) {
set_dialog_is_pinned(DialogListId(d->folder_id), d, false, false);
}
DialogDate dialog_date(d->order, d->dialog_id);
if (get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date) == 0) {
LOG_IF(ERROR, d->order != DEFAULT_ORDER) << d->dialog_id << " not found in the chat list";
}
do_set_dialog_folder_id(d, folder_id);
get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date);
update_dialog_lists(d, std::move(dialog_positions), true, false, "set_dialog_folder_id");
}
void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) {
CHECK(!td_->auth_manager_->is_bot());
if (d->folder_id == folder_id && d->is_folder_id_inited) {
return;
}
d->folder_id = folder_id;
d->is_folder_id_inited = true;
if (d->dialog_id.get_type() == DialogType::SecretChat) {
// need to change action bar only for the secret chat and keep unarchive for the main chat
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
if (d->is_update_new_chat_sent && user_id.is_valid()) {
const Dialog *user_d = get_dialog(DialogId(user_id));
if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) {
send_closure(
G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatActionBar>(d->dialog_id.get(), get_chat_action_bar_object(d)));
}
}
} else if (folder_id != FolderId::archive() && d->action_bar != nullptr && d->action_bar->on_dialog_unarchived()) {
send_update_chat_action_bar(d);
}
on_dialog_updated(d->dialog_id, "do_set_dialog_folder_id");
}
void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call,
bool is_group_call_empty, const char *source, bool force) {
LOG(INFO) << "Update voice chat in " << dialog_id << " with has_active_voice_chat = " << has_active_group_call
<< " and is_voice_chat_empty = " << is_group_call_empty << " from " << source;
CHECK(dialog_id.is_valid());
Dialog *d = get_dialog(dialog_id); // must not create the Dialog, because it is called from on_get_chat
if (d == nullptr) {
LOG(INFO) << "Can't find " << dialog_id;
pending_dialog_group_call_updates_[dialog_id] = {has_active_group_call, is_group_call_empty};
return;
}
if (!has_active_group_call) {
is_group_call_empty = false;
}
if (d->active_group_call_id.is_valid() && has_active_group_call && is_group_call_empty &&
(td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id) ||
td_->group_call_manager_->is_group_call_joined(d->active_group_call_id))) {
LOG(INFO) << "Fix is_group_call_empty to false";
is_group_call_empty = false;
}
if (d->has_active_group_call == has_active_group_call && d->is_group_call_empty == is_group_call_empty) {
return;
}
if (!force && d->active_group_call_id.is_valid() && has_active_group_call &&
td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) {
LOG(INFO) << "Ignore update in a being joined group call";
return;
}
if (d->has_active_group_call && !has_active_group_call && d->active_group_call_id.is_valid()) {
d->active_group_call_id = InputGroupCallId();
d->has_active_group_call = false;
d->is_group_call_empty = false;
send_update_chat_video_chat(d);
} else if (d->has_active_group_call && has_active_group_call) {
d->is_group_call_empty = is_group_call_empty;
send_update_chat_video_chat(d);
} else {
d->has_active_group_call = has_active_group_call;
d->is_group_call_empty = is_group_call_empty;
on_dialog_updated(dialog_id, "on_update_dialog_group_call");
if (has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) {
repair_dialog_active_group_call_id(dialog_id);
}
}
}
void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id) {
auto d = get_dialog_force(dialog_id, "on_update_dialog_group_call_id");
if (d == nullptr) {
// nothing to do
return;
}
if (d->active_group_call_id != input_group_call_id) {
LOG(INFO) << "Update active group call in " << dialog_id << " to " << input_group_call_id;
d->active_group_call_id = input_group_call_id;
bool has_active_group_call = input_group_call_id.is_valid();
if (has_active_group_call != d->has_active_group_call) {
d->has_active_group_call = has_active_group_call;
if (!has_active_group_call) {
d->is_group_call_empty = false;
}
}
send_update_chat_video_chat(d);
}
}
void MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id(DialogId dialog_id,
DialogId default_join_as_dialog_id,
bool force) {
auto d = get_dialog_force(dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id");
if (d == nullptr) {
// nothing to do
return;
}
if (!force && d->active_group_call_id.is_valid() &&
td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) {
LOG(INFO) << "Ignore default_join_as_dialog_id update in a being joined group call";
return;
}
if (default_join_as_dialog_id.is_valid()) {
if (default_join_as_dialog_id.get_type() != DialogType::User) {
force_create_dialog(default_join_as_dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id");
} else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id()) ||
default_join_as_dialog_id != get_my_dialog_id()) {
default_join_as_dialog_id = DialogId();
}
}
if (d->default_join_group_call_as_dialog_id != default_join_as_dialog_id) {
d->default_join_group_call_as_dialog_id = default_join_as_dialog_id;
send_update_chat_video_chat(d);
}
}
void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogId dialog_id,
DialogId default_send_as_dialog_id,
bool force) {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
auto dialog_type = dialog_id.get_type();
if (dialog_type != DialogType::Channel || is_broadcast_channel(dialog_id)) {
if (default_send_as_dialog_id != DialogId()) {
LOG(ERROR) << "Receive message sender " << default_send_as_dialog_id << " in " << dialog_id;
}
return;
}
auto d = get_dialog_force(dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
if (d == nullptr) {
// nothing to do
return;
}
if (!force) {
// TODO ignore update if have being sent messages
}
if (default_send_as_dialog_id.is_valid()) {
if (default_send_as_dialog_id.get_type() != DialogType::User) {
force_create_dialog(default_send_as_dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
} else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id()) ||
default_send_as_dialog_id != get_my_dialog_id()) {
default_send_as_dialog_id = DialogId();
}
}
if (d->default_send_message_as_dialog_id != default_send_as_dialog_id) {
if (force || default_send_as_dialog_id.is_valid() ||
(created_public_broadcasts_inited_ && created_public_broadcasts_.empty())) {
LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_as_dialog_id;
d->need_drop_default_send_message_as_dialog_id = false;
d->default_send_message_as_dialog_id = default_send_as_dialog_id;
send_update_chat_message_sender(d);
} else {
LOG(INFO) << "Postpone removal of message sender in " << dialog_id;
d->need_drop_default_send_message_as_dialog_id = true;
}
on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
} else if (default_send_as_dialog_id.is_valid() && d->need_drop_default_send_message_as_dialog_id) {
LOG(INFO) << "Don't remove message sender in " << dialog_id;
d->need_drop_default_send_message_as_dialog_id = false;
on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
}
}
void MessagesManager::on_update_dialog_message_ttl(DialogId dialog_id, MessageTtl message_ttl) {
auto d = get_dialog_force(dialog_id, "on_update_dialog_message_ttl");
if (d == nullptr) {
// nothing to do
return;
}
set_dialog_message_ttl(d, std::move(message_ttl));
}
void MessagesManager::set_dialog_message_ttl(Dialog *d, MessageTtl message_ttl) {
CHECK(d != nullptr);
if (d->message_ttl != message_ttl) {
d->message_ttl = message_ttl;
d->is_message_ttl_inited = true;
send_update_chat_message_ttl(d);
}
if (!d->is_message_ttl_inited) {
d->is_message_ttl_inited = true;
on_dialog_updated(d->dialog_id, "on_update_dialog_message_ttl");
}
}
void MessagesManager::on_update_dialog_filters() {
if (td_->auth_manager_->is_bot()) {
// just in case
return;
}
schedule_dialog_filters_reload(0.0);
}
void MessagesManager::on_create_new_dialog_success(int64 random_id, tl_object_ptr<telegram_api::Updates> &&updates,
DialogType expected_type, Promise<Unit> &&promise) {
auto sent_messages = UpdatesManager::get_new_messages(updates.get());
auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates.get());
if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u) {
LOG(ERROR) << "Receive wrong result for create group or channel chat " << oneline(to_string(updates));
return on_create_new_dialog_fail(random_id, Status::Error(500, "Unsupported server response"), std::move(promise));
}
auto *message = sent_messages.begin()->first;
// int64 message_random_id = *sent_messages_random_ids.begin();
// TODO check that message_random_id equals random_id after messages_createChat will be updated
if (sent_messages.begin()->second) {
return on_create_new_dialog_fail(random_id, Status::Error(500, "Scheduled message received"), std::move(promise));
}
auto dialog_id = DialogId::get_message_dialog_id(message);
if (dialog_id.get_type() != expected_type) {
return on_create_new_dialog_fail(random_id, Status::Error(500, "Chat of wrong type has been created"),
std::move(promise));
}
if (message->get_id() != telegram_api::messageService::ID) {
return on_create_new_dialog_fail(random_id, Status::Error(500, "Invalid message received"), std::move(promise));
}
auto action_id = static_cast<const telegram_api::messageService *>(message)->action_->get_id();
if (action_id != telegram_api::messageActionChatCreate::ID &&
action_id != telegram_api::messageActionChannelCreate::ID) {
return on_create_new_dialog_fail(random_id, Status::Error(500, "Invalid service message received"),
std::move(promise));
}
auto it = created_dialogs_.find(random_id);
CHECK(it != created_dialogs_.end());
CHECK(it->second == DialogId());
it->second = dialog_id;
const Dialog *d = get_dialog(dialog_id);
if (d != nullptr && d->last_new_message_id.is_valid()) {
// dialog have been already created and at least one non-temporary message was added,
// i.e. we are not interested in the creation of dialog by searchMessages
// then messages have already been added, so just set promise
return promise.set_value(Unit());
}
if (pending_created_dialogs_.count(dialog_id) == 0) {
pending_created_dialogs_.emplace(dialog_id, std::move(promise));
} else {
LOG(ERROR) << "Receive twice " << dialog_id << " as result of chat creation";
return on_create_new_dialog_fail(random_id, Status::Error(500, "Chat was created earlier"), std::move(promise));
}
td_->updates_manager_->on_get_updates(std::move(updates), Promise<Unit>());
}
void MessagesManager::on_create_new_dialog_fail(int64 random_id, Status error, Promise<Unit> &&promise) {
LOG(INFO) << "Clean up creation of group or channel chat";
auto it = created_dialogs_.find(random_id);
CHECK(it != created_dialogs_.end());
CHECK(it->second == DialogId());
created_dialogs_.erase(it);
CHECK(error.is_error());
promise.set_error(std::move(error));
// repairing state by running get difference
td_->updates_manager_->get_difference("on_create_new_dialog_fail");
}
void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector<UserId> bot_user_ids, bool from_database) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto d = from_database ? get_dialog(dialog_id) : get_dialog_force(dialog_id, "on_dialog_bots_updated");
if (d == nullptr) {
return;
}
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) {
auto m = get_message(d, message_id);
LOG_CHECK(m != nullptr) << d->dialog_id << ' ' << message_id;
send_update_message_content_impl(d->dialog_id, m, "set_dialog_has_bots");
}
}
}
}
void MessagesManager::on_dialog_photo_updated(DialogId dialog_id) {
auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatPhoto>(
dialog_id.get(), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(dialog_id))));
} else if (d != nullptr && d->is_update_new_chat_being_sent) {
const auto *photo = get_dialog_photo(dialog_id);
if (photo == nullptr) {
LOG(ERROR) << "Removed photo of " << dialog_id << " while the chat is being added";
} else {
LOG(ERROR) << "Changed photo of " << dialog_id << " while the chat is being added to " << *photo;
}
}
}
void MessagesManager::on_dialog_title_updated(DialogId dialog_id) {
auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr) {
update_dialogs_hints(d);
if (d->is_update_new_chat_sent) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatTitle>(dialog_id.get(), get_dialog_title(dialog_id)));
}
}
}
void MessagesManager::on_dialog_default_permissions_updated(DialogId dialog_id) {
auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatPermissions>(
dialog_id.get(), get_dialog_default_permissions(dialog_id).get_chat_permissions_object()));
}
}
void MessagesManager::on_dialog_has_protected_content_updated(DialogId dialog_id) {
auto d = get_dialog(dialog_id); // called from update_chat, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatHasProtectedContent>(
dialog_id.get(), get_dialog_has_protected_content(dialog_id)));
}
}
void MessagesManager::on_dialog_user_is_contact_updated(DialogId dialog_id, bool is_contact) {
CHECK(dialog_id.get_type() == DialogType::User);
auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
if (d->know_action_bar) {
if (is_contact) {
if (d->action_bar != nullptr && d->action_bar->on_user_contact_added()) {
send_update_chat_action_bar(d);
}
} else {
repair_dialog_action_bar(d, "on_dialog_user_is_contact_updated");
}
}
if (!dialog_filters_.empty() && d->order != DEFAULT_ORDER) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated");
td_->contacts_manager_->for_each_secret_chat_with_user(
d->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) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated");
}
});
}
}
}
void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool is_deleted) {
CHECK(dialog_id.get_type() == DialogType::User);
auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr && d->is_update_new_chat_sent) {
if (d->know_action_bar) {
if (is_deleted) {
if (d->action_bar != nullptr && d->action_bar->on_user_deleted()) {
send_update_chat_action_bar(d);
}
} else {
repair_dialog_action_bar(d, "on_dialog_user_is_deleted_updated");
}
}
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(
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) {
update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated");
}
});
}
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);
}
});
}
}
}
void MessagesManager::on_dialog_linked_channel_updated(DialogId dialog_id, ChannelId old_linked_channel_id,
ChannelId new_linked_channel_id) const {
CHECK(dialog_id.get_type() == DialogType::Channel);
if (td_->auth_manager_->is_bot() || !is_broadcast_channel(dialog_id)) {
return;
}
auto d = get_dialog(dialog_id); // no need to create the dialog
if (d == nullptr || !d->is_update_new_chat_sent) {
return;
}
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids, [old_linked_channel_id, new_linked_channel_id](const Message *m) {
return !m->reply_info.is_empty() && m->reply_info.channel_id_.is_valid() &&
(m->reply_info.channel_id_ == old_linked_channel_id || m->reply_info.channel_id_ == new_linked_channel_id);
});
LOG(INFO) << "Found discussion messages " << message_ids;
for (auto message_id : message_ids) {
send_update_message_interaction_info(dialog_id, get_message(d, message_id));
if (message_id == d->last_message_id) {
send_update_chat_last_message_impl(d, "on_dialog_linked_channel_updated");
}
}
}
DialogId MessagesManager::resolve_dialog_username(const string &username) const {
auto cleaned_username = clean_username(username);
auto resolved_username = resolved_usernames_.get(cleaned_username);
if (resolved_username.dialog_id.is_valid()) {
return resolved_username.dialog_id;
}
return inaccessible_resolved_usernames_.get(cleaned_username);
}
DialogId MessagesManager::search_public_dialog(const string &username_to_search, bool force, Promise<Unit> &&promise) {
string username = clean_username(username_to_search);
if (username[0] == '@') {
username = username.substr(1);
}
if (username.empty()) {
promise.set_error(Status::Error(200, "Username is invalid"));
return DialogId();
}
DialogId dialog_id;
auto resolved_username = resolved_usernames_.get(username);
if (resolved_username.dialog_id.is_valid()) {
if (resolved_username.expires_at < Time::now()) {
td_->create_handler<ResolveUsernameQuery>(Promise<Unit>())->send(username);
}
dialog_id = resolved_username.dialog_id;
} else {
dialog_id = inaccessible_resolved_usernames_.get(username);
}
if (dialog_id.is_valid()) {
if (have_input_peer(dialog_id, AccessRights::Read)) {
if (!force && reload_voice_chat_on_search_usernames_.count(username)) {
reload_voice_chat_on_search_usernames_.erase(username);
if (dialog_id.get_type() == DialogType::Channel) {
td_->contacts_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise),
"search_public_dialog");
return DialogId();
}
}
if (td_->auth_manager_->is_bot()) {
force_create_dialog(dialog_id, "search_public_dialog", true);
} else {
const Dialog *d = get_dialog_force(dialog_id, "search_public_dialog");
if (!is_dialog_inited(d)) {
send_get_dialog_query(dialog_id, std::move(promise), 0, "search_public_dialog");
return DialogId();
}
}
promise.set_value(Unit());
return dialog_id;
} else {
// bot username maybe known despite there is no access_hash
if (force || dialog_id.get_type() != DialogType::User) {
force_create_dialog(dialog_id, "search_public_dialog", true);
promise.set_value(Unit());
return dialog_id;
}
}
}
td_->create_handler<ResolveUsernameQuery>(std::move(promise))->send(username);
return DialogId();
}
void MessagesManager::reload_voice_chat_on_search(const string &username) {
if (!td_->auth_manager_->is_authorized()) {
return;
}
auto cleaned_username = clean_username(username);
if (!cleaned_username.empty()) {
reload_voice_chat_on_search_usernames_.insert(cleaned_username);
}
}
class MessagesManager::RegetDialogLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_reget_dialog_log_event(DialogId dialog_id) {
RegetDialogLogEvent log_event{dialog_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::RegetDialog, get_log_event_storer(log_event));
}
void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise, uint64 log_event_id,
const char *source) {
TRY_STATUS_PROMISE(promise, G()->close_status());
if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
if (log_event_id != 0) {
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
}
return promise.set_error(Status::Error(500, "Wrong getDialog query"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (log_event_id != 0) {
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
}
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
auto &promises = get_dialog_queries_[dialog_id];
promises.push_back(std::move(promise));
if (promises.size() != 1) {
if (log_event_id != 0) {
LOG(INFO) << "Duplicate getDialog query for " << dialog_id << " from " << source;
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
}
// query has already been sent, just wait for the result
return;
}
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_reget_dialog_log_event(dialog_id);
}
if (log_event_id != 0) {
auto result = get_dialog_query_log_event_id_.emplace(dialog_id, log_event_id);
CHECK(result.second);
}
if (!G()->close_flag()) {
LOG(INFO) << "Send get " << dialog_id << " query from " << source;
td_->create_handler<GetDialogQuery>()->send(dialog_id);
} else {
// request will be sent after restart
}
}
void MessagesManager::on_get_dialog_query_finished(DialogId dialog_id, Status &&status) {
if (G()->close_flag()) {
return;
}
LOG(INFO) << "Finished getting " << dialog_id << " with result " << status;
auto it = get_dialog_queries_.find(dialog_id);
CHECK(it != get_dialog_queries_.end());
CHECK(!it->second.empty());
auto promises = std::move(it->second);
get_dialog_queries_.erase(it);
auto log_event_it = get_dialog_query_log_event_id_.find(dialog_id);
if (log_event_it != get_dialog_query_log_event_id_.end()) {
if (!G()->close_flag()) {
binlog_erase(G()->td_db()->get_binlog(), log_event_it->second);
}
get_dialog_query_log_event_id_.erase(log_event_it);
}
if (status.is_ok()) {
set_promises(promises);
} else {
fail_promises(promises, std::move(status));
}
}
void MessagesManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames,
const Usernames &new_usernames) {
CHECK(dialog_id.is_valid());
const auto *d = get_dialog(dialog_id);
if (d != nullptr) {
update_dialogs_hints(d);
}
if (old_usernames != new_usernames) {
message_embedding_codes_[0].erase(dialog_id);
message_embedding_codes_[1].erase(dialog_id);
LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames;
}
if (!old_usernames.is_empty() && old_usernames != new_usernames) {
for (auto &username : old_usernames.get_active_usernames()) {
auto cleaned_username = clean_username(username);
resolved_usernames_.erase(cleaned_username);
inaccessible_resolved_usernames_.erase(cleaned_username);
}
}
if (!new_usernames.is_empty()) {
for (auto &username : new_usernames.get_active_usernames()) {
auto cleaned_username = clean_username(username);
if (!cleaned_username.empty()) {
resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + USERNAME_CACHE_EXPIRE_TIME};
}
}
}
}
void MessagesManager::on_resolved_username(const string &username, DialogId dialog_id) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Resolve username \"" << username << "\" to invalid " << dialog_id;
return;
}
auto cleaned_username = clean_username(username);
if (cleaned_username.empty()) {
return;
}
auto resolved_username = resolved_usernames_.get(cleaned_username);
if (resolved_username.dialog_id.is_valid()) {
LOG_IF(ERROR, resolved_username.dialog_id != dialog_id)
<< "Resolve username \"" << username << "\" to " << dialog_id << ", but have it in "
<< resolved_username.dialog_id;
return;
}
inaccessible_resolved_usernames_[cleaned_username] = dialog_id;
}
void MessagesManager::drop_username(const string &username) {
auto cleaned_username = clean_username(username);
if (cleaned_username.empty()) {
return;
}
inaccessible_resolved_usernames_.erase(cleaned_username);
auto resolved_username = resolved_usernames_.get(cleaned_username);
if (resolved_username.dialog_id.is_valid()) {
auto dialog_id = resolved_username.dialog_id;
if (have_input_peer(dialog_id, AccessRights::Read)) {
CHECK(dialog_id.get_type() != DialogType::SecretChat);
send_get_dialog_query(dialog_id, Auto(), 0, "drop_username");
}
resolved_usernames_.erase(cleaned_username);
}
}
const DialogPhoto *MessagesManager::get_dialog_photo(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return td_->contacts_manager_->get_user_dialog_photo(dialog_id.get_user_id());
case DialogType::Chat:
return td_->contacts_manager_->get_chat_dialog_photo(dialog_id.get_chat_id());
case DialogType::Channel:
return td_->contacts_manager_->get_channel_dialog_photo(dialog_id.get_channel_id());
case DialogType::SecretChat:
return td_->contacts_manager_->get_secret_chat_dialog_photo(dialog_id.get_secret_chat_id());
case DialogType::None:
default:
UNREACHABLE();
return nullptr;
}
}
string MessagesManager::get_dialog_title(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return td_->contacts_manager_->get_user_title(dialog_id.get_user_id());
case DialogType::Chat:
return td_->contacts_manager_->get_chat_title(dialog_id.get_chat_id());
case DialogType::Channel:
return td_->contacts_manager_->get_channel_title(dialog_id.get_channel_id());
case DialogType::SecretChat:
return td_->contacts_manager_->get_secret_chat_title(dialog_id.get_secret_chat_id());
case DialogType::None:
default:
UNREACHABLE();
return string();
}
}
RestrictedRights MessagesManager::get_dialog_default_permissions(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return td_->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id());
case DialogType::Chat:
return td_->contacts_manager_->get_chat_default_permissions(dialog_id.get_chat_id());
case DialogType::Channel:
return td_->contacts_manager_->get_channel_default_permissions(dialog_id.get_channel_id());
case DialogType::SecretChat:
return td_->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id());
case DialogType::None:
default:
UNREACHABLE();
return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false);
}
}
bool MessagesManager::get_dialog_has_protected_content(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
return false;
case DialogType::Chat:
return td_->contacts_manager_->get_chat_has_protected_content(dialog_id.get_chat_id());
case DialogType::Channel:
return td_->contacts_manager_->get_channel_has_protected_content(dialog_id.get_channel_id());
case DialogType::SecretChat:
return false;
case DialogType::None:
default:
UNREACHABLE();
return true;
}
}
bool MessagesManager::get_dialog_has_scheduled_messages(const Dialog *d) const {
if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
return false;
}
if (is_broadcast_channel(d->dialog_id) &&
!td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).can_post_messages()) {
return false;
}
// TODO send updateChatHasScheduledMessage when can_post_messages changes
return d->has_scheduled_server_messages || d->has_scheduled_database_messages || d->scheduled_messages != nullptr;
}
bool MessagesManager::is_dialog_action_unneeded(DialogId dialog_id) const {
if (is_anonymous_administrator(dialog_id, nullptr)) {
return true;
}
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
UserId user_id = dialog_type == DialogType::User
? dialog_id.get_user_id()
: td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (td_->contacts_manager_->is_user_deleted(user_id)) {
return true;
}
if (td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_support(user_id)) {
return true;
}
if (user_id == td_->contacts_manager_->get_my_id()) {
return true;
}
if (!td_->auth_manager_->is_bot()) {
if (td_->contacts_manager_->is_user_status_exact(user_id)) {
if (!td_->contacts_manager_->is_user_online(user_id, 30)) {
return true;
}
} else {
// return true;
}
}
}
return false;
}
void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action,
Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "send_dialog_action")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (top_thread_message_id != MessageId() &&
(!top_thread_message_id.is_valid() || !top_thread_message_id.is_server())) {
return promise.set_error(Status::Error(400, "Invalid message thread specified"));
}
tl_object_ptr<telegram_api::InputPeer> input_peer;
if (action == DialogAction::get_speaking_action()) {
input_peer = get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
return promise.set_error(Status::Error(400, "Have no access to the chat"));
}
} else {
auto can_send_status = can_send_message(dialog_id);
if (can_send_status.is_error()) {
if (td_->auth_manager_->is_bot()) {
return promise.set_error(std::move(can_send_status));
}
return promise.set_value(Unit());
}
if (is_dialog_action_unneeded(dialog_id)) {
LOG(INFO) << "Skip unneeded " << action << " in " << dialog_id;
return promise.set_value(Unit());
}
input_peer = get_input_peer(dialog_id, AccessRights::Write);
}
if (dialog_id.get_type() == DialogType::SecretChat) {
send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(),
action.get_secret_input_send_message_action());
promise.set_value(Unit());
return;
}
auto new_query_ref =
td_->create_handler<SetTypingQuery>(std::move(promise))
->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action());
if (td_->auth_manager_->is_bot()) {
return;
}
auto &query_ref = set_typing_query_[dialog_id];
if (!query_ref.empty()) {
LOG(INFO) << "Cancel previous send chat action query";
cancel_query(query_ref);
}
query_ref = std::move(new_query_ref);
}
void MessagesManager::after_set_typing_query(DialogId dialog_id, int32 generation) {
auto it = set_typing_query_.find(dialog_id);
if (it != set_typing_query_.end() && (!it->second.is_alive() || it->second.generation() == generation)) {
set_typing_query_.erase(it);
}
}
void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
LOG(INFO) << "Receive send_chat_action timeout in " << dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (can_send_message(dialog_id).is_error()) {
return;
}
auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
auto queue_it = yet_unsent_media_queues_.find(queue_id);
if (queue_it == yet_unsent_media_queues_.end()) {
return;
}
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 4.0);
CHECK(!queue_it->second.empty());
const Message *m = get_message(d, queue_it->second.begin()->first);
if (m == nullptr) {
return;
}
CHECK(m->message_id.is_yet_unsent());
if (m->forward_info != nullptr || m->had_forward_info || m->is_copy || m->message_id.is_scheduled() ||
m->sender_dialog_id.is_valid()) {
return;
}
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,
false, -1));
return;
}
auto file_view = td_->file_manager_->get_file_view(file_id);
if (!file_view.is_uploading()) {
return;
}
int64 total_size = file_view.expected_size();
int64 uploaded_size = file_view.remote_size();
int32 progress = 0;
if (total_size > 0 && uploaded_size > 0) {
if (uploaded_size > total_size) {
uploaded_size = total_size; // just in case
}
progress = static_cast<int32>(100 * uploaded_size / total_size);
}
DialogAction action = DialogAction::get_uploading_action(m->content->get_type(), progress);
if (action == DialogAction()) {
return;
}
LOG(INFO) << "Send " << action << " in " << dialog_id;
send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), Promise<Unit>());
}
void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) {
LOG(DEBUG) << "Receive active dialog action timeout in " << dialog_id;
auto actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
return;
}
CHECK(!actions_it->second.empty());
auto now = Time::now();
DialogId prev_typing_dialog_id;
while (actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT < now + 0.1) {
CHECK(actions_it->second[0].typing_dialog_id != prev_typing_dialog_id);
prev_typing_dialog_id = actions_it->second[0].typing_dialog_id;
on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id,
DialogAction(), 0);
actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
return;
}
CHECK(!actions_it->second.empty());
}
LOG(DEBUG) << "Schedule next action timeout in " << dialog_id;
active_dialog_action_timeout_.add_timeout_in(dialog_id.get(),
actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT - now);
}
void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) {
LOG(DEBUG) << "Clear active dialog actions in " << dialog_id;
auto actions_it = active_dialog_actions_.find(dialog_id);
while (actions_it != active_dialog_actions_.end()) {
CHECK(!actions_it->second.empty());
on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id,
DialogAction(), 0);
actions_it = active_dialog_actions_.find(dialog_id);
}
}
vector<DialogListId> MessagesManager::get_dialog_lists_to_add_dialog(DialogId dialog_id) {
vector<DialogListId> result;
const Dialog *d = get_dialog_force(dialog_id, "get_dialog_lists_to_add_dialog");
if (d == nullptr || d->order == DEFAULT_ORDER || !have_input_peer(dialog_id, AccessRights::Read)) {
return result;
}
if (dialog_id != get_my_dialog_id() && dialog_id != DialogId(ContactsManager::get_service_notifications_user_id())) {
result.push_back(DialogListId(d->folder_id == FolderId::archive() ? FolderId::main() : FolderId::archive()));
}
for (const auto &dialog_filter : dialog_filters_) {
auto dialog_filter_id = dialog_filter->dialog_filter_id;
if (!InputDialogId::contains(dialog_filter->included_dialog_ids, dialog_id) &&
!InputDialogId::contains(dialog_filter->pinned_dialog_ids, dialog_id)) {
// the dialog isn't added yet to the dialog list
// check that it can be actually added
if (dialog_filter->included_dialog_ids.size() + dialog_filter->pinned_dialog_ids.size() <
narrow_cast<size_t>(DialogFilter::get_max_filter_dialogs())) {
// fast path
result.push_back(DialogListId(dialog_filter_id));
continue;
}
auto new_dialog_filter = make_unique<DialogFilter>(*dialog_filter);
new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
if (new_dialog_filter->check_limits().is_ok()) {
result.push_back(DialogListId(dialog_filter_id));
}
}
}
return result;
}
void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog_list_id, Promise<Unit> &&promise) {
LOG(INFO) << "Receive addChatToList request to add " << dialog_id << " to " << dialog_list_id;
CHECK(!td_->auth_manager_->is_bot());
Dialog *d = get_dialog_force(dialog_id, "add_dialog_to_list");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (d->order == DEFAULT_ORDER) {
return promise.set_error(Status::Error(400, "Chat is not in a chat list"));
}
if (get_dialog_list(dialog_list_id) == nullptr) {
return promise.set_error(Status::Error(400, "Chat list not found"));
}
if (dialog_list_id.is_filter()) {
CHECK(is_update_chat_filters_sent_);
auto dialog_filter_id = dialog_list_id.get_filter_id();
auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
CHECK(old_dialog_filter != nullptr);
if (InputDialogId::contains(old_dialog_filter->included_dialog_ids, dialog_id) ||
InputDialogId::contains(old_dialog_filter->pinned_dialog_ids, dialog_id)) {
return promise.set_value(Unit());
}
auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
auto status = new_dialog_filter->check_limits();
if (status.is_error()) {
return promise.set_error(std::move(status));
}
sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "add_dialog_to_list");
edit_dialog_filter(std::move(new_dialog_filter), "add_dialog_to_list");
save_dialog_filters();
send_update_chat_filters();
if (dialog_id.get_type() != DialogType::SecretChat) {
synchronize_dialog_filters();
}
return promise.set_value(Unit());
}
CHECK(dialog_list_id.is_folder());
auto folder_id = dialog_list_id.get_folder_id();
if (d->folder_id == folder_id) {
return promise.set_value(Unit());
}
if (folder_id == FolderId::archive() &&
(dialog_id == get_my_dialog_id() ||
dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()))) {
return promise.set_error(Status::Error(400, "Chat can't be archived"));
}
set_dialog_folder_id(d, folder_id);
if (dialog_id.get_type() != DialogType::SecretChat) {
set_dialog_folder_id_on_server(dialog_id, false);
}
promise.set_value(Unit());
}
class MessagesManager::SetDialogFolderIdOnServerLogEvent {
public:
DialogId dialog_id_;
FolderId folder_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
td::store(folder_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
td::parse(folder_id_, parser);
}
};
void MessagesManager::set_dialog_folder_id_on_server(DialogId dialog_id, bool from_binlog) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!from_binlog && G()->parameters().use_message_db) {
SetDialogFolderIdOnServerLogEvent log_event;
log_event.dialog_id_ = dialog_id;
log_event.folder_id_ = d->folder_id;
add_log_event(d->set_folder_id_log_event_id, get_log_event_storer(log_event),
LogEvent::HandlerType::SetDialogFolderIdOnServer, "set chat folder");
}
Promise<Unit> promise;
if (d->set_folder_id_log_event_id.log_event_id != 0) {
d->set_folder_id_log_event_id.generation++;
promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
generation = d->set_folder_id_log_event_id.generation](Result<Unit> result) {
if (!G()->close_flag()) {
send_closure(actor_id, &MessagesManager::on_updated_dialog_folder_id, dialog_id, generation);
}
});
}
// TODO do not send two queries simultaneously or use InvokeAfter
td_->create_handler<EditPeerFoldersQuery>(std::move(promise))->send(dialog_id, d->folder_id);
}
void MessagesManager::on_updated_dialog_folder_id(DialogId dialog_id, uint64 generation) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
delete_log_event(d->set_folder_id_log_event_id, generation, "set chat folder");
}
void MessagesManager::set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputChatPhoto> &input_photo,
Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "set_dialog_photo")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
return promise.set_error(Status::Error(400, "Can't change private chat photo"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat photo"));
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat photo"));
}
break;
}
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't change secret chat photo"));
case DialogType::None:
default:
UNREACHABLE();
}
const td_api::object_ptr<td_api::InputFile> *input_file = nullptr;
double main_frame_timestamp = 0.0;
bool is_animation = false;
if (input_photo != nullptr) {
switch (input_photo->get_id()) {
case td_api::inputChatPhotoPrevious::ID: {
auto photo = static_cast<const td_api::inputChatPhotoPrevious *>(input_photo.get());
auto file_id = td_->contacts_manager_->get_profile_photo_file_id(photo->chat_photo_id_);
if (!file_id.is_valid()) {
return promise.set_error(Status::Error(400, "Unknown profile photo ID specified"));
}
auto file_view = td_->file_manager_->get_file_view(file_id);
auto input_chat_photo =
make_tl_object<telegram_api::inputChatPhoto>(file_view.main_remote_location().as_input_photo());
send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
return;
}
case td_api::inputChatPhotoStatic::ID: {
auto photo = static_cast<const td_api::inputChatPhotoStatic *>(input_photo.get());
input_file = &photo->photo_;
break;
}
case td_api::inputChatPhotoAnimation::ID: {
auto photo = static_cast<const td_api::inputChatPhotoAnimation *>(input_photo.get());
input_file = &photo->animation_;
main_frame_timestamp = photo->main_frame_timestamp_;
is_animation = true;
break;
}
default:
UNREACHABLE();
break;
}
}
if (input_file == nullptr) {
send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object<telegram_api::inputChatPhotoEmpty>(),
std::move(promise));
return;
}
const double MAX_ANIMATION_DURATION = 10.0;
if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) {
return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified"));
}
auto file_type = is_animation ? FileType::Animation : FileType::Photo;
auto r_file_id = td_->file_manager_->get_input_file_id(file_type, *input_file, dialog_id, true, false);
if (r_file_id.is_error()) {
// TODO promise.set_error(std::move(status));
return promise.set_error(Status::Error(400, r_file_id.error().message()));
}
FileId file_id = r_file_id.ok();
if (!file_id.is_valid()) {
send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object<telegram_api::inputChatPhotoEmpty>(),
std::move(promise));
return;
}
upload_dialog_photo(dialog_id, td_->file_manager_->dup_file_id(file_id, "set_dialog_photo"), is_animation,
main_frame_timestamp, false, std::move(promise));
}
void MessagesManager::send_edit_dialog_photo_query(DialogId dialog_id, FileId file_id,
tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo,
Promise<Unit> &&promise) {
// TODO invoke after
td_->create_handler<EditDialogPhotoQuery>(std::move(promise))->send(dialog_id, file_id, std::move(input_chat_photo));
}
void MessagesManager::upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation,
double main_frame_timestamp, bool is_reupload, Promise<Unit> &&promise,
vector<int> bad_parts) {
CHECK(file_id.is_valid());
LOG(INFO) << "Ask to upload chat photo " << file_id;
bool is_inserted = being_uploaded_dialog_photos_
.emplace(file_id, UploadedDialogPhotoInfo{dialog_id, main_frame_timestamp, is_animation,
is_reupload, std::move(promise)})
.second;
CHECK(is_inserted);
// TODO use force_reupload if is_reupload
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_dialog_photo_callback_, 32, 0);
}
void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "set_dialog_title")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
return promise.set_error(Status::Error(400, "Title must be non-empty"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
return promise.set_error(Status::Error(400, "Can't change private chat title"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat title"));
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat title"));
}
break;
}
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't change secret chat title"));
case DialogType::None:
default:
UNREACHABLE();
}
// TODO this can be wrong if there were previous change title requests
if (get_dialog_title(dialog_id) == new_title) {
return promise.set_value(Unit());
}
// TODO invoke after
td_->create_handler<EditDialogTitleQuery>(std::move(promise))->send(dialog_id, new_title);
}
void MessagesManager::set_dialog_available_reactions(
DialogId dialog_id, td_api::object_ptr<td_api::ChatAvailableReactions> &&available_reactions_ptr,
Promise<Unit> &&promise) {
Dialog *d = get_dialog_force(dialog_id, "set_dialog_available_reactions");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
ChatReactions available_reactions(std::move(available_reactions_ptr), !is_broadcast_channel(dialog_id));
auto active_reactions = get_active_reactions(available_reactions);
if (active_reactions.reactions_.size() != available_reactions.reactions_.size()) {
return promise.set_error(Status::Error(400, "Invalid reactions specified"));
}
available_reactions = std::move(active_reactions);
switch (dialog_id.get_type()) {
case DialogType::User:
return promise.set_error(Status::Error(400, "Can't change private chat available reactions"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions"));
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions"));
}
break;
}
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't change secret chat available reactions"));
case DialogType::None:
default:
UNREACHABLE();
}
bool is_changed = d->available_reactions != available_reactions;
set_dialog_available_reactions(d, ChatReactions(available_reactions));
if (!is_changed) {
return promise.set_value(Unit());
}
// TODO invoke after
td_->create_handler<SetChatAvailableReactionsQuery>(std::move(promise))
->send(dialog_id, std::move(available_reactions));
}
void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise<Unit> &&promise) {
if (ttl < 0) {
return promise.set_error(Status::Error(400, "Message auto-delete time can't be negative"));
}
Dialog *d = get_dialog_force(dialog_id, "set_dialog_message_ttl");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return promise.set_error(Status::Error(400, "Have no write access to the chat"));
}
LOG(INFO) << "Begin to set message TTL in " << dialog_id << " to " << ttl;
switch (dialog_id.get_type()) {
case DialogType::User:
if (dialog_id == get_my_dialog_id() ||
dialog_id == DialogId(ContactsManager::get_service_notifications_user_id())) {
return promise.set_error(Status::Error(400, "Message auto-delete time in the chat can't be changed"));
}
break;
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings()) {
return promise.set_error(
Status::Error(400, "Not enough rights to change message auto-delete time in the chat"));
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
return promise.set_error(
Status::Error(400, "Not enough rights to change message auto-delete time in the chat"));
}
break;
}
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (dialog_id.get_type() != DialogType::SecretChat) {
// TODO invoke after
td_->create_handler<SetHistoryTtlQuery>(std::move(promise))->send(dialog_id, ttl);
} else {
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
create_chat_set_ttl_message_content(ttl, UserId()), &need_update_dialog_pos);
send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "set_dialog_message_ttl");
}
int64 random_id = begin_send_message(dialog_id, m);
send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(),
ttl, random_id, std::move(promise));
}
}
void MessagesManager::set_dialog_permissions(DialogId dialog_id,
const td_api::object_ptr<td_api::chatPermissions> &permissions,
Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "set_dialog_permissions")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (permissions == nullptr) {
return promise.set_error(Status::Error(400, "New permissions must be non-empty"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
return promise.set_error(Status::Error(400, "Can't change private chat permissions"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_restrict_members()) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions"));
}
break;
}
case DialogType::Channel: {
if (is_broadcast_channel(dialog_id)) {
return promise.set_error(Status::Error(400, "Can't change channel chat permissions"));
}
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_restrict_members()) {
return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions"));
}
break;
}
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't change secret chat permissions"));
case DialogType::None:
default:
UNREACHABLE();
}
RestrictedRights new_permissions(permissions);
// TODO this can be wrong if there were previous change permissions requests
if (get_dialog_default_permissions(dialog_id) == new_permissions) {
return promise.set_value(Unit());
}
// TODO invoke after
td_->create_handler<EditChatDefaultBannedRightsQuery>(std::move(promise))->send(dialog_id, new_permissions);
}
void MessagesManager::toggle_dialog_has_protected_content(DialogId dialog_id, bool has_protected_content,
Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "toggle_dialog_has_protected_content")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't restrict saving content in the chat"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_status(chat_id);
if (!status.is_creator()) {
return promise.set_error(Status::Error(400, "Only owner can restrict saving content"));
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
if (!status.is_creator()) {
return promise.set_error(Status::Error(400, "Only owner can restrict saving content"));
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
// TODO this can be wrong if there were previous toggle_dialog_has_protected_content requests
if (get_dialog_has_protected_content(dialog_id) == has_protected_content) {
return promise.set_value(Unit());
}
// TODO invoke after
td_->create_handler<ToggleNoForwardsQuery>(std::move(promise))->send(dialog_id, has_protected_content);
}
void MessagesManager::set_dialog_theme(DialogId dialog_id, const string &theme_name, Promise<Unit> &&promise) {
auto d = get_dialog_force(dialog_id, "set_dialog_theme");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
case DialogType::Channel:
return promise.set_error(Status::Error(400, "Can't change theme in the chat"));
case DialogType::SecretChat: {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (!user_id.is_valid()) {
return promise.set_error(Status::Error(400, "Can't access the user"));
}
dialog_id = DialogId(user_id);
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
// TODO this can be wrong if there were previous change theme requests
if (get_dialog_theme_name(d) == theme_name) {
return promise.set_value(Unit());
}
// TODO invoke after
td_->create_handler<SetChatThemeQuery>(std::move(promise))->send(dialog_id, theme_name);
}
void MessagesManager::set_dialog_description(DialogId dialog_id, const string &description, Promise<Unit> &&promise) {
if (!have_dialog_force(dialog_id, "set_dialog_description")) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
return promise.set_error(Status::Error(400, "Can't change private chat description"));
case DialogType::Chat:
return td_->contacts_manager_->set_chat_description(dialog_id.get_chat_id(), description, std::move(promise));
case DialogType::Channel:
return td_->contacts_manager_->set_channel_description(dialog_id.get_channel_id(), description,
std::move(promise));
case DialogType::SecretChat:
return promise.set_error(Status::Error(400, "Can't change secret chat description"));
case DialogType::None:
default:
UNREACHABLE();
}
}
Status MessagesManager::can_pin_messages(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_pin_messages() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
return Status::Error(400, "Not enough rights to manage pinned messages in the chat");
}
break;
}
case DialogType::Channel: {
auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
bool can_pin = is_broadcast_channel(dialog_id) ? status.can_edit_messages() : status.can_pin_messages();
if (!can_pin) {
return Status::Error(400, "Not enough rights to manage pinned messages in the chat");
}
break;
}
case DialogType::SecretChat:
return Status::Error(400, "Secret chats can't have pinned messages");
case DialogType::None:
default:
UNREACHABLE();
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
return Status::Error(400, "Not enough rights");
}
return Status::OK();
}
void MessagesManager::pin_dialog_message(DialogId dialog_id, MessageId message_id, bool disable_notification,
bool only_for_self, bool is_unpin, Promise<Unit> &&promise) {
auto d = get_dialog_force(dialog_id, "pin_dialog_message");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id));
const Message *m = get_message_force(d, message_id, "pin_dialog_message");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Scheduled message can't be pinned"));
}
if (!message_id.is_server()) {
return promise.set_error(Status::Error(400, "Message can't be pinned"));
}
if (is_service_message_content(m->content->get_type())) {
return promise.set_error(Status::Error(400, "A service message can't be pinned"));
}
if (only_for_self && dialog_id.get_type() != DialogType::User) {
return promise.set_error(Status::Error(400, "Messages can't be pinned only for self in the chat"));
}
// TODO log event
td_->create_handler<UpdateDialogPinnedMessageQuery>(std::move(promise))
->send(dialog_id, message_id, is_unpin, disable_notification, only_for_self);
}
void MessagesManager::unpin_all_dialog_messages(DialogId dialog_id, MessageId top_thread_message_id,
Promise<Unit> &&promise) {
auto d = get_dialog_force(dialog_id, "unpin_all_dialog_messages");
if (d == nullptr) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id));
TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
if (!td_->auth_manager_->is_bot()) {
vector<MessageId> message_ids;
find_messages(d->messages.get(), message_ids, [top_thread_message_id](const Message *m) {
return m->is_pinned && (!top_thread_message_id.is_valid() ||
(m->is_topic_message && m->top_thread_message_id == top_thread_message_id));
});
vector<int64> deleted_message_ids;
for (auto message_id : message_ids) {
auto m = get_message(d, message_id);
CHECK(m != nullptr);
m->is_pinned = false;
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageIsPinned>(d->dialog_id.get(), m->message_id.get(), m->is_pinned));
on_message_changed(d, m, true, "unpin_all_dialog_messages");
}
}
if (top_thread_message_id.is_valid()) {
AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
Promise<AffectedHistory> &&query_promise) {
td->create_handler<UnpinAllMessagesQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
return;
}
set_dialog_last_pinned_message_id(d, MessageId());
if (d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] != 0) {
d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] = 0;
on_dialog_updated(dialog_id, "unpin_all_dialog_messages");
}
unpin_all_dialog_messages_on_server(dialog_id, 0, std::move(promise));
}
class MessagesManager::UnpinAllDialogMessagesOnServerLogEvent {
public:
DialogId dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id_, parser);
}
};
uint64 MessagesManager::save_unpin_all_dialog_messages_on_server_log_event(DialogId dialog_id) {
UnpinAllDialogMessagesOnServerLogEvent log_event{dialog_id};
return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UnpinAllDialogMessagesOnServer,
get_log_event_storer(log_event));
}
void MessagesManager::unpin_all_dialog_messages_on_server(DialogId dialog_id, uint64 log_event_id,
Promise<Unit> &&promise) {
if (log_event_id == 0 && G()->parameters().use_message_db) {
log_event_id = save_unpin_all_dialog_messages_on_server_log_event(dialog_id);
}
AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
td->create_handler<UnpinAllMessagesQuery>(std::move(query_promise))->send(dialog_id, MessageId());
};
run_affected_history_query_until_complete(dialog_id, std::move(query), true,
get_erase_log_event_promise(log_event_id, std::move(promise)));
}
unique_ptr<MessagesManager::Message> *MessagesManager::treap_find_message(unique_ptr<Message> *v,
MessageId message_id) {
return const_cast<unique_ptr<Message> *>(treap_find_message(static_cast<const unique_ptr<Message> *>(v), message_id));
}
const unique_ptr<MessagesManager::Message> *MessagesManager::treap_find_message(const unique_ptr<Message> *v,
MessageId message_id) {
while (*v != nullptr) {
if ((*v)->message_id.get() < message_id.get()) {
v = &(*v)->right;
} else if ((*v)->message_id.get() > message_id.get()) {
v = &(*v)->left;
} else {
break;
}
}
return v;
}
MessagesManager::Message *MessagesManager::treap_insert_message(unique_ptr<Message> *v, unique_ptr<Message> message) {
auto message_id = message->message_id;
while (*v != nullptr && (*v)->random_y >= message->random_y) {
if ((*v)->message_id.get() < message_id.get()) {
v = &(*v)->right;
} else if ((*v)->message_id == message_id) {
UNREACHABLE();
} else {
v = &(*v)->left;
}
}
unique_ptr<Message> *left = &message->left;
unique_ptr<Message> *right = &message->right;
unique_ptr<Message> cur = std::move(*v);
while (cur != nullptr) {
if (cur->message_id.get() < message_id.get()) {
*left = std::move(cur);
left = &((*left)->right);
cur = std::move(*left);
} else {
*right = std::move(cur);
right = &((*right)->left);
cur = std::move(*right);
}
}
CHECK(*left == nullptr);
CHECK(*right == nullptr);
*v = std::move(message);
return v->get();
}
unique_ptr<MessagesManager::Message> MessagesManager::treap_delete_message(unique_ptr<Message> *v) {
unique_ptr<Message> result = std::move(*v);
unique_ptr<Message> left = std::move(result->left);
unique_ptr<Message> right = std::move(result->right);
while (left != nullptr || right != nullptr) {
if (left == nullptr || (right != nullptr && right->random_y > left->random_y)) {
*v = std::move(right);
v = &((*v)->left);
right = std::move(*v);
} else {
*v = std::move(left);
v = &((*v)->right);
left = std::move(*v);
}
}
CHECK(*v == nullptr);
return result;
}
MessagesManager::Message *MessagesManager::get_message(Dialog *d, MessageId message_id) {
return const_cast<Message *>(get_message(static_cast<const Dialog *>(d), message_id));
}
const MessagesManager::Message *MessagesManager::get_message(const Dialog *d, MessageId message_id) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return nullptr;
}
CHECK(d != nullptr);
bool is_scheduled = message_id.is_scheduled();
if (is_scheduled && message_id.is_scheduled_server()) {
auto server_message_id = message_id.get_scheduled_server_message_id();
auto it = d->scheduled_message_date.find(server_message_id);
if (it != d->scheduled_message_date.end()) {
int32 date = it->second;
message_id = MessageId(server_message_id, date);
CHECK(message_id.is_scheduled_server());
}
}
auto result = treap_find_message(is_scheduled ? &d->scheduled_messages : &d->messages, message_id)->get();
if (result != nullptr && !is_scheduled) {
result->last_access_date = G()->unix_time_cached();
}
LOG(INFO) << "Search for " << message_id << " in " << d->dialog_id << " found " << result;
return result;
}
MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageId message_id, const char *source) {
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return nullptr;
}
auto result = get_message(d, message_id);
if (result != nullptr) {
return result;
}
if (!G()->parameters().use_message_db || message_id.is_yet_unsent() || is_deleted_message(d, message_id)) {
return nullptr;
}
if (message_id.is_scheduled() && d->has_loaded_scheduled_messages_from_database) {
return nullptr;
}
LOG(INFO) << "Trying to load " << FullMessageId{d->dialog_id, message_id} << " from database from " << source;
auto r_value = G()->td_db()->get_message_db_sync()->get_message({d->dialog_id, message_id});
if (r_value.is_error()) {
return nullptr;
}
return on_get_message_from_database(d, r_value.ok(), message_id.is_scheduled(), source);
}
MessagesManager::Message *MessagesManager::on_get_message_from_database(const MessageDbMessage &message,
bool is_scheduled, const char *source) {
if (message.data.empty()) {
return nullptr;
}
auto dialog_id = message.dialog_id;
Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
LOG(ERROR) << "Can't find " << dialog_id << ", but have a message from it from " << source;
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Got message in invalid " << dialog_id << " from " << source;
return nullptr;
}
bool is_valid_server_message_id =
(is_scheduled ? message.message_id.is_valid_scheduled() && message.message_id.is_scheduled_server()
: message.message_id.is_valid() && message.message_id.is_server());
if (is_valid_server_message_id &&
(dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::Chat)) {
get_message_from_server({dialog_id, message.message_id}, Auto(), "on_get_message_from_database 1");
}
force_create_dialog(dialog_id, source);
d = get_dialog_force(dialog_id, source);
CHECK(d != nullptr);
}
return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source);
}
MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d,
const MessageDbDialogMessage &message,
bool is_scheduled, const char *source) {
return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source);
}
MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d, MessageId expected_message_id,
const BufferSlice &value, bool is_scheduled,
const char *source) {
if (value.empty()) {
return nullptr;
}
auto m = parse_message(d, expected_message_id, value, is_scheduled);
if (m == nullptr) {
return nullptr;
}
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return nullptr;
}
auto old_message = get_message(d, m->message_id);
if (old_message != nullptr) {
// data in the database is always outdated, so return a message from the memory
if (dialog_id.get_type() == DialogType::SecretChat) {
CHECK(!is_scheduled);
// just in case restore random_id to message_id corespondence
// can be needed if there was newer unloaded message with the same random_id
add_random_id_to_message_id_correspondence(d, old_message->random_id, old_message->message_id);
}
if (old_message->notification_id.is_valid() && !is_scheduled) {
add_notification_id_to_message_id_correspondence(d, old_message->notification_id, old_message->message_id);
}
return old_message;
}
Dependencies dependencies;
add_message_dependencies(dependencies, m.get());
if (!dependencies.resolve_force(td_, "on_get_message_from_database") &&
dialog_id.get_type() != DialogType::SecretChat) {
get_message_from_server({dialog_id, m->message_id}, Auto(), "on_get_message_from_database 2");
}
m->have_previous = false;
m->have_next = false;
m->from_database = true;
bool need_update = false;
bool need_update_dialog_pos = false;
auto result = add_message_to_dialog(d, std::move(m), false, &need_update, &need_update_dialog_pos, source);
if (need_update_dialog_pos) {
LOG(ERROR) << "Need update dialog pos after load " << (result == nullptr ? MessageId() : result->message_id)
<< " in " << dialog_id << " from " << source;
send_update_chat_last_message(d, source);
}
return result;
}
int32 MessagesManager::get_random_y(MessageId message_id) {
return static_cast<int32>(static_cast<uint32>(message_id.get() * 2101234567u));
}
void MessagesManager::set_message_id(unique_ptr<Message> &message, MessageId message_id) {
message->message_id = message_id;
message->random_y = get_random_y(message_id);
}
MessagesManager::Message *MessagesManager::add_message_to_dialog(DialogId dialog_id, unique_ptr<Message> message,
bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source) {
CHECK(message != nullptr);
CHECK(dialog_id.get_type() != DialogType::None);
CHECK(need_update_dialog_pos != nullptr);
MessageId message_id = message->message_id;
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "invalid message identifier";
return nullptr;
}
Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
if (from_update) {
CHECK(!being_added_by_new_message_dialog_id_.is_valid());
being_added_by_new_message_dialog_id_ = dialog_id;
}
d = add_dialog(dialog_id, source);
*need_update_dialog_pos = true;
being_added_by_new_message_dialog_id_ = DialogId();
} else {
CHECK(d->dialog_id == dialog_id);
}
return add_message_to_dialog(d, std::move(message), from_update, need_update, need_update_dialog_pos, source);
}
// keep synced with add_scheduled_message_to_dialog
MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, unique_ptr<Message> message,
bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source) {
CHECK(message != nullptr);
CHECK(d != nullptr);
CHECK(need_update != nullptr);
CHECK(need_update_dialog_pos != nullptr);
CHECK(source != nullptr);
debug_add_message_to_dialog_fail_reason_ = "success";
auto debug_have_previous = message->have_previous;
auto debug_have_next = message->have_next;
DialogId dialog_id = d->dialog_id;
MessageId message_id = message->message_id;
if (!has_message_sender_user_id(dialog_id, message.get()) && !message->sender_dialog_id.is_valid()) {
if (is_broadcast_channel(dialog_id)) {
message->sender_dialog_id = dialog_id;
} else {
if (is_discussion_message(dialog_id, message.get())) {
message->sender_dialog_id = message->forward_info->from_dialog_id;
} else {
LOG(ERROR) << "Failed to repair sender chat in " << message_id << " in " << dialog_id;
}
}
}
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_type == DialogType::Channel && !is_broadcast_channel(dialog_id)) {
message->sender_user_id = UserId();
message->sender_dialog_id = dialog_id;
}
if (!message->from_database && message_id.is_valid()) {
switch (dialog_type) {
case DialogType::Chat:
case DialogType::Channel: {
message->available_reactions_generation = d->available_reactions_generation;
break;
}
case DialogType::User:
case DialogType::SecretChat:
break;
default:
UNREACHABLE();
break;
}
message->history_generation = d->history_generation;
}
if (!message->top_thread_message_id.is_valid() && is_thread_message(dialog_id, message.get())) {
message->top_thread_message_id = message_id;
}
if (!message_id.is_scheduled() && message_id <= d->last_clear_history_message_id) {
LOG(INFO) << "Skip adding cleared " << message_id << " to " << dialog_id << " from " << source;
if (message->from_database) {
delete_message_from_database(d, message_id, message.get(), true);
}
debug_add_message_to_dialog_fail_reason_ = "cleared full history";
return nullptr;
}
LOG(INFO) << "Adding " << message_id << " of type " << message->content->get_type() << " to " << dialog_id << " from "
<< source << ". Last new is " << d->last_new_message_id << ", last is " << d->last_message_id
<< ", from_update = " << from_update << ", have_previous = " << message->have_previous
<< ", have_next = " << message->have_next;
if (!message_id.is_valid()) {
if (message_id.is_valid_scheduled()) {
return add_scheduled_message_to_dialog(d, std::move(message), from_update, need_update, source);
}
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " from " << source;
CHECK(!message->from_database);
debug_add_message_to_dialog_fail_reason_ = "invalid message identifier";
return nullptr;
}
if (*need_update) {
CHECK(from_update);
}
if (is_deleted_message(d, message_id)) {
LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "adding deleted message";
return nullptr;
}
auto message_content_type = message->content->get_type();
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::Add, message_id, message_content_type, from_update,
message->have_previous, message->have_next, source);
}
message->last_access_date = G()->unix_time_cached();
if (from_update) {
CHECK(message->have_next);
CHECK(message->have_previous);
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)) &&
d->last_new_message_id >= MessageId(ServerMessageId(2147483000))) {
LOG(FATAL) << "Force restart because of message_id overflow in " << dialog_id << " from "
<< d->last_new_message_id << " to " << message_id;
}
if (!has_qts_messages(dialog_id)) {
LOG(ERROR) << "New " << message_id << " in " << dialog_id << " from " << source
<< " has identifier less than last_new_message_id = " << d->last_new_message_id;
dump_debug_message_op(d);
}
}
}
}
if (!from_update && !message->is_failed_to_send) {
MessageId max_message_id;
if (message_id.is_server()) {
if (d->being_added_message_id.is_valid()) {
// if a too new message not from update has failed to preload before being_added_message_id was set,
// then it should fail to load even after it is set and last_new_message_id has changed
max_message_id = d->being_updated_last_new_message_id;
} else {
max_message_id = d->last_new_message_id;
}
} else if (message_id.is_local()) {
if (d->being_added_message_id.is_valid()) {
max_message_id = d->being_updated_last_database_message_id;
} else {
max_message_id = d->last_database_message_id;
}
}
if (max_message_id != MessageId() && message_id > max_message_id) {
if (!message->from_database) {
if (!has_qts_messages(dialog_id)) {
LOG(ERROR) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source
<< ". The maximum allowed is " << max_message_id << ", last is " << d->last_message_id
<< ", being added message is " << d->being_added_message_id << ", channel difference "
<< debug_channel_difference_dialog_ << " "
<< to_string(get_message_object(dialog_id, message.get(), "add_message_to_dialog"));
dump_debug_message_op(d, 3);
}
if (need_channel_difference_to_add_message(dialog_id, nullptr)) {
LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
} else {
LOG(INFO) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source;
}
debug_add_message_to_dialog_fail_reason_ = "too new message not from update";
return nullptr;
}
}
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) {
delete_message_from_database(d, message_id, message.get(), true);
}
debug_add_message_to_dialog_fail_reason_ = "ignore unavailable message";
return nullptr;
}
if (message_content_type == MessageContentType::ChatDeleteHistory) {
{
auto m = delete_message(d, message_id, true, need_update_dialog_pos, "message chat delete history");
if (m != nullptr) {
send_update_delete_messages(dialog_id, {m->message_id.get()}, true);
}
}
int32 last_message_date = 0;
if (d->last_message_id != MessageId()) {
auto last_message = get_message(d, d->last_message_id);
CHECK(last_message != nullptr);
last_message_date = last_message->date - 1;
} else {
last_message_date = d->last_clear_history_date;
}
if (message->date > last_message_date) {
set_dialog_last_clear_history_date(d, message->date, message_id, "update_last_clear_history_date");
*need_update_dialog_pos = true;
}
LOG(INFO) << "Process MessageChatDeleteHistory in " << message_id << " in " << dialog_id << " with date "
<< message->date << " from " << source;
if (message_id > d->max_unavailable_message_id) {
set_dialog_max_unavailable_message_id(dialog_id, message_id, false, "message chat delete history");
}
CHECK(!message->from_database);
debug_add_message_to_dialog_fail_reason_ = "skip adding MessageChatDeleteHistory";
return nullptr;
}
if (*need_update && message_id <= d->last_new_message_id && !td_->auth_manager_->is_bot()) {
*need_update = false;
}
if (message->reply_markup != nullptr &&
(message->reply_markup->type == ReplyMarkup::Type::RemoveKeyboard ||
(message->reply_markup->type == ReplyMarkup::Type::ForceReply && !message->reply_markup->is_personal)) &&
!td_->auth_manager_->is_bot()) {
if (from_update && message->reply_markup->is_personal) { // if this keyboard is for us
if (d->reply_markup_message_id != MessageId() && message_id > d->reply_markup_message_id) {
const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 1");
if (old_message == nullptr ||
(old_message->sender_user_id.is_valid() && old_message->sender_user_id == message->sender_user_id)) {
set_dialog_reply_markup(d, MessageId());
}
}
}
message->had_reply_markup = message->reply_markup->is_personal;
message->reply_markup = nullptr;
}
bool auto_attach = message->have_previous && message->have_next &&
(from_update || message_id.is_local() || message_id.is_yet_unsent());
{
Message *m = message->from_database ? get_message(d, message_id)
: get_message_force(d, message_id, "add_message_to_dialog 2");
if (m != nullptr) {
CHECK(m->message_id == message_id);
CHECK(message->message_id == message_id);
LOG(INFO) << "Adding already existing " << message_id << " in " << dialog_id << " from " << source;
if (*need_update) {
*need_update = false;
if (!G()->parameters().use_message_db) {
// can happen if the message is received first through getMessage in an unknown chat without
// last_new_message_id and only after that received through getDifference or getChannelDifference
if (d->last_new_message_id.is_valid()) {
LOG(ERROR) << "Receive again " << (message->is_outgoing ? "outgoing" : "incoming")
<< (message->forward_info == nullptr ? " not" : "") << " forwarded " << message_id
<< " with content of type " << message_content_type << " in " << dialog_id << " from " << source
<< ", current last new is " << d->last_new_message_id << ", last is " << d->last_message_id;
dump_debug_message_op(d, 1);
}
}
}
if (auto_attach) {
CHECK(message->have_previous);
CHECK(message->have_next);
message->have_previous = false;
message->have_next = false;
}
if (!message->from_database && (from_update || message->edit_date >= m->edit_date)) {
const int32 INDEX_MASK_MASK = ~(message_search_filter_index_mask(MessageSearchFilter::UnreadMention) |
message_search_filter_index_mask(MessageSearchFilter::UnreadReaction));
auto old_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK;
bool was_deleted = delete_active_live_location(dialog_id, m);
auto old_file_ids = get_message_content_file_ids(m->content.get(), td_);
bool need_send_update = update_message(d, m, std::move(message), need_update_dialog_pos, true);
if (!need_send_update) {
LOG(INFO) << message_id << " in " << dialog_id << " is not changed";
}
auto new_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK;
if (was_deleted) {
try_add_active_live_location(dialog_id, m);
}
change_message_files(dialog_id, m, old_file_ids);
if (need_send_update) {
on_message_notification_changed(d, m, source);
}
update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask);
update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask);
}
return m;
}
}
if (*need_update && !td_->auth_manager_->is_bot()) {
if (message_content_type == MessageContentType::PinMessage) {
if (is_dialog_pinned_message_notifications_disabled(d) ||
!get_message_content_pinned_message_id(message->content.get()).is_valid()) {
// treat message pin without pinned message as an ordinary message
message->contains_mention = false;
}
} else if (message->contains_mention && is_dialog_mention_notifications_disabled(d)) {
// disable mention notification
message->is_mention_notification_disabled = true;
}
}
if (message->contains_unread_mention && message_id <= d->last_read_all_mentions_message_id) {
LOG(INFO) << "Ignore unread mention in " << message_id;
message->contains_unread_mention = false;
if (message->from_database) {
on_message_changed(d, message.get(), false, "add already read mention message to dialog");
}
}
if (*need_update && may_need_message_notification(d, message.get())) {
// notification group must be created here because it may force adding new messages from database
// in get_message_notification_group_force
get_dialog_notification_group_id(d->dialog_id, get_notification_group_info(d, message.get()));
}
if (*need_update || (!d->last_new_message_id.is_valid() && !message_id.is_yet_unsent() && from_update)) {
auto pinned_message_id = get_message_content_pinned_message_id(message->content.get());
if (pinned_message_id.is_valid() && pinned_message_id < message_id &&
have_message_force(d, pinned_message_id, "preload pinned message")) {
LOG(INFO) << "Preloaded pinned " << pinned_message_id << " from database";
}
if (d->pinned_message_notification_message_id.is_valid() &&
d->pinned_message_notification_message_id != message_id &&
have_message_force(d, d->pinned_message_notification_message_id, "preload previously pinned message")) {
LOG(INFO) << "Preloaded previously pinned " << d->pinned_message_notification_message_id << " from database";
}
}
if (from_update && message->top_thread_message_id.is_valid() && message->top_thread_message_id != message_id &&
message_id.is_server() && have_message_force(d, message->top_thread_message_id, "preload top reply message")) {
LOG(INFO) << "Preloaded top thread " << message->top_thread_message_id << " from database";
Message *top_m = get_message(d, message->top_thread_message_id);
CHECK(top_m != nullptr);
if (is_active_message_reply_info(dialog_id, top_m->reply_info) && is_discussion_message(dialog_id, top_m)) {
FullMessageId top_full_message_id{top_m->forward_info->from_dialog_id, top_m->forward_info->from_message_id};
if (have_message_force(top_full_message_id, "preload discussed message")) {
LOG(INFO) << "Preloaded discussed " << top_full_message_id << " from database";
}
}
}
// there must be no two recursive calls to add_message_to_dialog
LOG_CHECK(!d->being_added_message_id.is_valid())
<< d->dialog_id << " " << d->being_added_message_id << " " << message_id << " " << *need_update << " "
<< d->pinned_message_notification_message_id << " " << d->last_new_message_id << " " << source;
LOG_CHECK(!d->being_deleted_message_id.is_valid())
<< d->being_deleted_message_id << " " << message_id << " " << source;
d->being_added_message_id = message_id;
d->being_updated_last_new_message_id = d->last_new_message_id;
d->being_updated_last_database_message_id = d->last_database_message_id;
if (d->new_secret_chat_notification_id.is_valid()) {
remove_new_secret_chat_notification(d, true);
}
if (message->message_id > d->max_added_message_id) {
d->max_added_message_id = message->message_id;
}
if (d->have_full_history && !message->from_database && !from_update && !message_id.is_local() &&
!message_id.is_yet_unsent()) {
LOG(ERROR) << "Have full history in " << dialog_id << ", but receive unknown " << message_id
<< " with content of type " << message_content_type << " from " << source << ". Last new is "
<< d->last_new_message_id << ", last is " << d->last_message_id << ", first database is "
<< d->first_database_message_id << ", last database is " << d->last_database_message_id
<< ", last read inbox is " << d->last_read_inbox_message_id << ", last read outbox is "
<< d->last_read_inbox_message_id << ", last read all mentions is "
<< d->last_read_all_mentions_message_id << ", max unavailable is " << d->max_unavailable_message_id
<< ", last assigned is " << d->last_assigned_message_id << ", last clear history date is "
<< d->last_clear_history_date << ", last clear history is " << d->last_clear_history_message_id
<< ", last delete is " << d->deleted_last_message_id << ", delete last message date is "
<< d->delete_last_message_date << ", have_full_history source = " << d->have_full_history_source;
d->have_full_history = false;
d->have_full_history_source = 0;
on_dialog_updated(dialog_id, "drop have_full_history");
}
if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled() && !d->has_unload_timeout) {
LOG(INFO) << "Schedule unload of " << dialog_id;
pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d));
d->has_unload_timeout = true;
}
if (message->ttl > 0 && message->ttl_expires_at != 0) {
auto now = Time::now();
if (message->ttl_expires_at <= now) {
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";
d->being_added_message_id = MessageId();
return nullptr;
} else {
on_message_ttl_expired_impl(d, message.get());
message_content_type = message->content->get_type();
if (message->from_database) {
on_message_changed(d, message.get(), false, "add expired message to dialog");
}
}
} else {
ttl_register_message(dialog_id, message.get(), now);
}
}
if (message->ttl_period > 0) {
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;
delete_message_from_database(d, message_id, message.get(), true);
debug_add_message_to_dialog_fail_reason_ = "delete expired by TTL period message";
d->being_added_message_id = MessageId();
return nullptr;
} else {
ttl_period_register_message(dialog_id, message.get(), server_time);
}
}
if (message->from_database && !message->are_media_timestamp_entities_found) {
auto text = get_message_content_text_mutable(message->content.get());
if (text != nullptr) {
fix_formatted_text(text->text, text->entities, true, true, true, false, false).ensure();
// always call to save are_media_timestamp_entities_found flag
on_message_changed(d, message.get(), false, "save media timestamp entities");
}
}
message->are_media_timestamp_entities_found = true;
LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source;
if (d->is_empty) {
d->is_empty = false;
*need_update_dialog_pos = true;
}
if (dialog_type == DialogType::Channel && !message->contains_unread_mention) {
auto channel_read_media_period =
td_->option_manager_->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) {
update_opened_message_content(message->content.get());
}
}
if (G()->parameters().use_file_db && message_id.is_yet_unsent() && !message->via_bot_user_id.is_valid() &&
!message->hide_via_bot && !message->is_copy) {
auto queue_id = ChainId(dialog_id, message_content_type).get();
if (queue_id & 1) {
LOG(INFO) << "Add " << message_id << " from " << source << " to queue " << queue_id;
yet_unsent_media_queues_[queue_id][message_id]; // reserve place for promise
if (!td_->auth_manager_->is_bot()) {
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 1.0);
}
}
}
if (!(d->have_full_history && auto_attach) && d->last_message_id.is_valid() &&
d->last_message_id < MessageId(ServerMessageId(1)) && message_id >= MessageId(ServerMessageId(1))) {
set_dialog_last_message_id(d, MessageId(), "add_message_to_dialog");
set_dialog_first_database_message_id(d, MessageId(), "add_message_to_dialog");
set_dialog_last_database_message_id(d, MessageId(), source);
d->have_full_history = false;
d->have_full_history_source = 0;
invalidate_message_indexes(d);
d->local_unread_count = 0; // read all local messages. They will not be reachable anymore
on_dialog_updated(dialog_id, "add gap to dialog");
send_update_chat_last_message(d, "add gap to dialog");
*need_update_dialog_pos = false;
}
if (from_update && message_id > d->last_new_message_id && !message_id.is_yet_unsent()) {
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");
}
}
bool is_attached = false;
if (auto_attach) {
auto it = MessagesIterator(d, message_id);
Message *previous_message = *it;
if (previous_message != nullptr) {
auto previous_message_id = previous_message->message_id;
CHECK(previous_message_id < message_id);
if (previous_message->have_next || (d->last_message_id.is_valid() && previous_message_id >= d->last_message_id)) {
if (message_id.is_server() && previous_message_id.is_server() && previous_message->have_next) {
++it;
auto next_message = *it;
if (next_message != nullptr) {
if (next_message->message_id.is_server() && !has_qts_messages(dialog_id)) {
LOG(ERROR) << "Attach " << message_id << " from " << source << " before " << next_message->message_id
<< " and after " << previous_message_id << " in " << dialog_id;
dump_debug_message_op(d);
}
} else {
LOG(ERROR) << "Have_next is true, but there is no next message after " << previous_message_id << " from "
<< source << " in " << dialog_id;
dump_debug_message_op(d);
}
}
LOG(INFO) << "Attach " << message_id << " to the previous " << previous_message_id << " in " << dialog_id;
message->have_previous = true;
message->have_next = previous_message->have_next;
previous_message->have_next = true;
is_attached = true;
}
}
if (!is_attached && !message_id.is_yet_unsent()) {
// message may be attached to the next message if there is no previous message
Message *cur = d->messages.get();
Message *next_message = nullptr;
while (cur != nullptr) {
if (cur->message_id < message_id) {
cur = cur->right.get();
} else {
next_message = cur;
cur = cur->left.get();
}
}
if (next_message != nullptr) {
CHECK(!next_message->have_previous);
LOG(INFO) << "Attach " << message_id << " to the next " << next_message->message_id << " in " << dialog_id;
if (from_update && !next_message->message_id.is_yet_unsent() && !has_qts_messages(dialog_id)) {
LOG(ERROR) << "Attach " << message_id << " from " << source << " to the next " << next_message->message_id
<< " in " << dialog_id;
}
message->have_next = true;
message->have_previous = next_message->have_previous;
next_message->have_previous = true;
is_attached = true;
}
}
if (!is_attached) {
LOG(INFO) << "Can't auto-attach " << message_id << " in " << dialog_id;
message->have_previous = false;
message->have_next = false;
}
}
if (!td_->auth_manager_->is_bot()) {
if (*need_update) {
// notification must be added before updating unread_count to have correct total notification count
// in get_message_notification_group_force
add_new_message_notification(d, message.get(), false);
} else {
if (message->from_database && message->notification_id.is_valid() &&
is_from_mention_notification_group(message.get()) && is_message_notification_active(d, message.get()) &&
is_dialog_mention_notifications_disabled(d) && message_id != d->pinned_message_notification_message_id) {
auto notification_id = message->notification_id;
VLOG(notifications) << "Remove mention " << notification_id << " in " << message_id << " in " << dialog_id;
message->notification_id = NotificationId();
if (d->mention_notification_group.last_notification_id == notification_id) {
// last notification is deleted, need to find new last notification
fix_dialog_last_notification_id(d, true, message_id);
}
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification,
d->mention_notification_group.group_id, notification_id, false, false, Promise<Unit>(),
"remove disabled mention notification");
on_message_changed(d, message.get(), false, "remove_mention_notification");
}
}
}
const Message *m = message.get();
if (*need_update && message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) {
if (has_incoming_notification(dialog_id, m)) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
if (message_id.is_server()) {
server_unread_count++;
} else {
local_unread_count++;
}
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
source);
} else {
// if non-scheduled outgoing message has identifier one greater than last_read_inbox_message_id,
// then definitely there are no unread incoming messages before it
if (message_id.is_server() && d->last_read_inbox_message_id.is_valid() &&
d->last_read_inbox_message_id.is_server() &&
message_id == d->last_read_inbox_message_id.get_next_message_id(MessageType::Server)) {
read_history_inbox(dialog_id, message_id, 0, "add_message_to_dialog");
}
}
}
if (*need_update && m->contains_unread_mention) {
set_dialog_unread_mention_count(d, d->unread_mention_count + 1);
send_update_chat_unread_mention_count(d);
}
if (*need_update && has_unread_message_reactions(dialog_id, m)) {
set_dialog_unread_reaction_count(d, d->unread_reaction_count + 1);
send_update_chat_unread_reaction_count(d, "add_message_to_dialog");
}
if (*need_update) {
update_message_count_by_index(d, +1, m);
}
if (auto_attach && message_id > d->last_message_id && message_id >= d->last_new_message_id) {
set_dialog_last_message_id(d, message_id, "add_message_to_dialog", m);
*need_update_dialog_pos = true;
}
if (auto_attach && !message_id.is_yet_unsent() && message_id >= d->last_new_message_id &&
(d->last_new_message_id.is_valid() ||
(message_id.is_local() && d->last_message_id.is_valid() &&
(message_id >= d->last_message_id ||
(d->last_database_message_id.is_valid() && message_id > d->last_database_message_id))))) {
CHECK(message_id <= d->last_message_id);
if (message_id > d->last_database_message_id) {
set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog");
if (!d->first_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog");
try_restore_dialog_reply_markup(d, m);
}
}
}
if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) {
if (!m->reply_to_message_id.is_yet_unsent()) {
if (!m->reply_to_message_id.is_scheduled()) {
replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++;
}
} else {
replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id);
}
}
if (!m->from_database && !m->message_id.is_yet_unsent()) {
add_message_to_database(d, m, "add_message_to_dialog");
}
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
repair_channel_server_unread_count(d);
}
if (m->date + 3600 >= now && m->is_outgoing) {
auto channel_id = dialog_id.get_channel_id();
auto slow_mode_delay = td_->contacts_manager_->get_channel_slow_mode_delay(channel_id);
auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
if (m->date + slow_mode_delay > now && !status.is_administrator()) {
td_->contacts_manager_->on_update_channel_slow_mode_next_send_date(channel_id, m->date + slow_mode_delay);
}
}
if (m->date > now - 14 * 86400) {
td_->contacts_manager_->remove_inactive_channel(dialog_id.get_channel_id());
}
}
if (!is_attached && !m->have_next && !m->have_previous) {
MessagesIterator it(d, m->message_id);
if (*it != nullptr && (*it)->have_next) {
// need to drop a connection between messages
auto previous_message = *it;
++it;
auto next_message = *it;
if (next_message != nullptr) {
if (next_message->message_id.is_server() &&
!(td_->auth_manager_->is_bot() && Slice(source) == Slice("GetRepliedChannelMessageQuery"))) {
LOG(ERROR) << "Can't attach " << m->message_id << " of type " << m->content->get_type() << " from " << source
<< " from " << (m->from_database ? "database" : "server") << " before " << next_message->message_id
<< " and after " << previous_message->message_id << " in " << dialog_id;
dump_debug_message_op(d);
}
next_message->have_previous = false;
previous_message->have_next = false;
} else {
LOG(ERROR) << "Have_next is true, but there is no next message after " << previous_message->message_id
<< " from " << source << " in " << dialog_id;
dump_debug_message_op(d);
}
} else if (m->message_id.is_server() && d->last_message_id.is_valid() && m->message_id > d->last_message_id) {
LOG(INFO) << "Receive " << m->message_id << ", which is newer than the last " << d->last_message_id
<< " not from update";
set_dialog_last_message_id(d, MessageId(), source);
if (m->message_id > d->deleted_last_message_id) {
d->delete_last_message_date = m->date;
d->deleted_last_message_id = message_id;
}
set_dialog_first_database_message_id(d, MessageId(), source);
set_dialog_last_database_message_id(d, MessageId(), source);
d->have_full_history = false;
d->have_full_history_source = 0;
invalidate_message_indexes(d);
on_dialog_updated(dialog_id, source);
send_update_chat_last_message(d, source);
*need_update_dialog_pos = false;
on_dialog_updated(d->dialog_id, "do delete last message");
if (!td_->auth_manager_->is_bot()) {
send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, d->dialog_id, false, false,
Promise<Unit>());
}
}
}
if (message_content_type == MessageContentType::ContactRegistered && !d->has_contact_registered_message) {
d->has_contact_registered_message = true;
on_dialog_updated(dialog_id, "update_has_contact_registered_message");
}
reget_message_from_server_if_needed(dialog_id, m);
add_message_file_sources(dialog_id, m);
register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_message_to_dialog");
register_message_reply(dialog_id, m);
if (*need_update && m->message_id.is_server() && message_content_type == MessageContentType::PinMessage) {
auto pinned_message_id = get_message_content_pinned_message_id(m->content.get());
if (d->is_last_pinned_message_id_inited && pinned_message_id > d->last_pinned_message_id) {
set_dialog_last_pinned_message_id(d, pinned_message_id);
}
}
if (*need_update && m->message_id.is_server() && message_content_type == MessageContentType::ChatSetTheme) {
set_dialog_theme_name(d, get_message_content_theme_name(m->content.get()));
}
if (from_update) {
speculatively_update_active_group_call_id(d, m);
speculatively_update_channel_participants(dialog_id, m);
update_forum_topic_info_by_service_message_content(td_, m->content.get(), dialog_id, m->top_thread_message_id);
update_sent_message_contents(dialog_id, m);
update_used_hashtags(dialog_id, m);
update_top_dialogs(dialog_id, m);
cancel_dialog_action(dialog_id, m);
update_has_outgoing_messages(dialog_id, m);
if (!td_->auth_manager_->is_bot() && d->messages == nullptr && !m->is_outgoing && dialog_id != get_my_dialog_id()) {
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(), Promise<Unit>());
break;
case DialogType::Chat:
case DialogType::Channel:
// nothing to do
break;
case DialogType::SecretChat: {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (user_id.is_valid()) {
td_->contacts_manager_->invalidate_user_full(user_id);
td_->contacts_manager_->reload_user_full(user_id, Promise<Unit>());
}
break;
}
case DialogType::None:
default:
UNREACHABLE();
}
}
}
if (m->is_topic_message) {
td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, m->top_thread_message_id, +1);
}
Message *result_message = treap_insert_message(&d->messages, std::move(message));
CHECK(result_message != nullptr);
CHECK(result_message == m);
CHECK(d->messages != nullptr);
if (!is_attached) {
if (m->have_next) {
LOG_CHECK(!m->have_previous) << auto_attach << " " << dialog_id << " " << m->message_id << " " << from_update
<< " " << *need_update << " " << d->being_updated_last_new_message_id << " "
<< d->last_new_message_id << " " << d->being_updated_last_database_message_id << " "
<< d->last_database_message_id << " " << debug_have_previous << " "
<< debug_have_next << " " << source;
attach_message_to_next(d, m->message_id, source);
} else if (m->have_previous) {
attach_message_to_previous(d, m->message_id, source);
}
}
if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid()) {
auto is_inserted = d->yet_unsent_thread_message_ids[m->top_thread_message_id].insert(m->message_id).second;
CHECK(is_inserted);
}
switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
if (m->message_id.is_server()) {
message_id_to_dialog_id_.set(m->message_id, dialog_id);
}
break;
case DialogType::Channel:
// nothing to do
break;
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (m->notification_id.is_valid()) {
add_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id);
}
if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) {
add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
}
try_add_bot_command_message_id(dialog_id, m);
// must be called after the message is added to correctly update replies
update_message_max_reply_media_timestamp(d, result_message, false);
update_message_max_own_media_timestamp(d, result_message);
result_message->debug_source = source;
d->being_added_message_id = MessageId();
if (!td_->auth_manager_->is_bot() && from_update && d->reply_markup_message_id != MessageId()) {
auto deleted_user_id = get_message_content_deleted_user_id(m->content.get());
if (deleted_user_id.is_valid()) { // do not check for is_user_bot to allow deleted bots
const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 3");
if (old_message == nullptr || old_message->sender_user_id == deleted_user_id) {
LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot " << deleted_user_id
<< " isn't a member of the chat";
set_dialog_reply_markup(d, MessageId());
}
}
}
added_message_count_++;
return result_message;
}
MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialog *d, unique_ptr<Message> message,
bool from_update, bool *need_update,
const char *source) {
CHECK(message != nullptr);
CHECK(d != nullptr);
CHECK(need_update != nullptr);
CHECK(source != nullptr);
DialogId dialog_id = d->dialog_id;
MessageId message_id = message->message_id;
CHECK(message_id.is_valid_scheduled());
CHECK(!message->notification_id.is_valid());
CHECK(!message->removed_notification_id.is_valid());
if (!message_id.is_yet_unsent()) {
message->top_thread_message_id = MessageId();
}
if (is_deleted_message(d, message_id)) {
LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "adding deleted scheduled message";
return nullptr;
}
if (dialog_id.get_type() == DialogType::SecretChat) {
LOG(ERROR) << "Tried to add " << message_id << " to " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message to secret chat";
return nullptr;
}
if (message->ttl != 0 || message->ttl_expires_at != 0) {
LOG(ERROR) << "Tried to add " << message_id << " with TTL " << message->ttl << "/" << message->ttl_expires_at
<< " to " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "skip adding secret scheduled message";
return nullptr;
}
if (message->ttl_period != 0) {
LOG(ERROR) << "Tried to add " << message_id << " with TTL period " << message->ttl_period << " to " << dialog_id
<< " from " << source;
debug_add_message_to_dialog_fail_reason_ = "skip adding auto-deleting scheduled message";
return nullptr;
}
if (td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Bot tried to add " << message_id << " to " << dialog_id << " from " << source;
debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message by bot";
return nullptr;
}
auto message_content_type = message->content->get_type();
if (is_service_message_content(message_content_type) || message_content_type == MessageContentType::LiveLocation ||
message_content_type == MessageContentType::ExpiredPhoto ||
message_content_type == MessageContentType::ExpiredVideo) {
LOG(ERROR) << "Tried to add " << message_id << " of type " << message_content_type << " to " << dialog_id
<< " from " << source;
debug_add_message_to_dialog_fail_reason_ = "skip adding message of unexpected type";
return nullptr;
}
{
Message *m = message->from_database ? get_message(d, message_id)
: get_message_force(d, message_id, "add_scheduled_message_to_dialog");
if (m != nullptr) {
auto old_message_id = m->message_id;
LOG(INFO) << "Adding already existing " << old_message_id << " in " << dialog_id << " from " << source;
set_message_id(message, old_message_id);
if (!message->from_database) {
auto old_file_ids = get_message_content_file_ids(m->content.get(), td_);
bool need_update_dialog_pos = false;
update_message(d, m, std::move(message), &need_update_dialog_pos, true);
CHECK(need_update_dialog_pos == false);
change_message_files(dialog_id, m, old_file_ids);
}
if (old_message_id != message_id) {
being_readded_message_id_ = {dialog_id, old_message_id};
message = do_delete_scheduled_message(d, old_message_id, false, "add_scheduled_message_to_dialog");
CHECK(message != nullptr);
send_update_delete_messages(dialog_id, {message->message_id.get()}, false);
set_message_id(message, message_id);
message->from_database = false;
} else {
*need_update = false;
return m;
}
}
}
LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source;
const Message *m = message.get();
if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) {
if (!m->reply_to_message_id.is_yet_unsent()) {
if (!m->reply_to_message_id.is_scheduled()) {
replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++;
}
} else {
replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id);
}
}
if (!m->from_database && !m->message_id.is_yet_unsent()) {
add_message_to_database(d, m, "add_scheduled_message_to_dialog");
}
reget_message_from_server_if_needed(dialog_id, m);
add_message_file_sources(dialog_id, m);
register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_scheduled_message_to_dialog");
if (m->message_id.is_yet_unsent()) {
add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
}
// must be called after register_message_content, which loads web page
update_message_max_reply_media_timestamp(d, message.get(), false);
update_message_max_own_media_timestamp(d, message.get());
register_message_reply(dialog_id, m);
if (from_update) {
update_sent_message_contents(dialog_id, m);
update_used_hashtags(dialog_id, m);
update_has_outgoing_messages(dialog_id, m);
}
if (m->message_id.is_scheduled_server()) {
int32 &date = d->scheduled_message_date[m->message_id.get_scheduled_server_message_id()];
CHECK(date == 0);
date = m->date;
}
if (m->is_topic_message) {
td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, m->top_thread_message_id, +1);
}
Message *result_message = treap_insert_message(&d->scheduled_messages, std::move(message));
CHECK(result_message != nullptr);
CHECK(d->scheduled_messages != nullptr);
being_readded_message_id_ = FullMessageId();
return result_message;
}
void MessagesManager::register_new_local_message_id(Dialog *d, const Message *m) {
if (m == nullptr) {
return;
}
if (m->message_id.is_scheduled()) {
return;
}
CHECK(m->message_id.is_local());
if (m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) {
Message *top_m = get_message_force(d, m->top_thread_message_id, "register_new_local_message_id");
if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) {
auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(),
m->message_id);
if (it == top_m->local_thread_message_ids.end() || *it != m->message_id) {
top_m->local_thread_message_ids.insert(it, m->message_id);
if (top_m->local_thread_message_ids.size() >= 1000) {
top_m->local_thread_message_ids.erase(top_m->local_thread_message_ids.begin());
}
on_message_changed(d, top_m, false, "register_new_local_message_id");
}
}
}
}
void MessagesManager::on_message_changed(const Dialog *d, const Message *m, bool need_send_update, const char *source) {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (need_send_update && m->message_id == d->last_message_id) {
send_update_chat_last_message_impl(d, source);
}
if (m->message_id == d->last_database_message_id) {
on_dialog_updated(d->dialog_id, source);
}
if (!m->message_id.is_yet_unsent()) {
add_message_to_database(d, m, source);
}
}
void MessagesManager::on_message_notification_changed(Dialog *d, const Message *m, const char *source) {
CHECK(d != nullptr);
CHECK(m != nullptr);
if (m->notification_id.is_valid() && is_message_notification_active(d, m)) {
auto &group_info = get_notification_group_info(d, m);
if (group_info.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id,
m->notification_id,
create_new_message_notification(
m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m))));
}
}
if (m->is_pinned && d->pinned_message_notification_message_id.is_valid() &&
d->mention_notification_group.group_id.is_valid()) {
auto pinned_message = get_message_force(d, d->pinned_message_notification_message_id, "after update_message");
if (pinned_message != nullptr && pinned_message->notification_id.is_valid() &&
is_message_notification_active(d, pinned_message) &&
get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) {
send_closure_later(
G()->notification_manager(), &NotificationManager::edit_notification, d->mention_notification_group.group_id,
pinned_message->notification_id,
create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true)));
}
}
}
void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, const char *source) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(d != nullptr);
CHECK(m != nullptr);
MessageId message_id = m->message_id;
if (message_id.is_scheduled()) {
LOG(INFO) << "Add " << FullMessageId(d->dialog_id, message_id) << " to database from " << source;
set_dialog_has_scheduled_database_messages(d->dialog_id, true);
G()->td_db()->get_message_db_async()->add_scheduled_message({d->dialog_id, message_id}, log_event_store(*m),
Auto()); // TODO Promise
return;
}
LOG_CHECK(message_id.is_server() || message_id.is_local()) << source;
LOG(INFO) << "Add " << FullMessageId(d->dialog_id, message_id) << " to database from " << source;
ServerMessageId unique_message_id;
int64 random_id = 0;
int64 search_id = 0;
string text;
switch (d->dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
if (message_id.is_server()) {
unique_message_id = message_id.get_server_message_id();
}
// FOR DEBUG
// text = get_message_search_text(m);
// if (!text.empty()) {
// search_id = (static_cast<int64>(m->date) << 32) | static_cast<uint32>(Random::secure_int32());
// }
break;
case DialogType::Channel:
break;
case DialogType::SecretChat:
random_id = m->random_id;
text = get_message_search_text(m);
if (!text.empty()) {
search_id = (static_cast<int64>(m->date) << 32) | static_cast<uint32>(m->random_id);
}
break;
case DialogType::None:
default:
UNREACHABLE();
}
int32 ttl_expires_at = 0;
if (m->ttl_expires_at != 0) {
ttl_expires_at = static_cast<int32>(m->ttl_expires_at - Time::now() + G()->server_time()) + 1;
}
if (m->ttl_period != 0 && (ttl_expires_at == 0 || m->date + m->ttl_period < ttl_expires_at)) {
ttl_expires_at = m->date + m->ttl_period;
}
G()->td_db()->get_message_db_async()->add_message({d->dialog_id, message_id}, unique_message_id,
get_message_sender(m), random_id, ttl_expires_at,
get_message_index_mask(d->dialog_id, m), search_id, text,
m->notification_id, m->top_thread_message_id, log_event_store(*m),
Auto()); // TODO Promise
}
void MessagesManager::delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id,
const char *source) {
CHECK(d != nullptr);
CHECK(max_message_id.is_valid());
if (d->new_secret_chat_notification_id.is_valid()) {
remove_new_secret_chat_notification(d, true);
}
if (d->pinned_message_notification_message_id.is_valid() &&
d->pinned_message_notification_message_id <= max_message_id) {
remove_dialog_pinned_message_notification(d, source);
}
remove_message_dialog_notifications(d, max_message_id, false, source);
remove_message_dialog_notifications(d, max_message_id, true, source);
if (!G()->parameters().use_message_db) {
return;
}
auto dialog_id = d->dialog_id;
LOG(INFO) << "Delete all messages in " << dialog_id << " from database up to " << max_message_id << " from "
<< source;
/*
if (dialog_id.get_type() == DialogType::User && max_message_id.is_server()) {
bool need_save = false;
int i = 0;
for (auto &first_message_id : calls_db_state_.first_calls_database_message_id_by_index) {
if (first_message_id <= max_message_id) {
first_message_id = max_message_id.get_next_server_message_id();
calls_db_state_.message_count_by_index[i] = -1;
need_save = true;
}
i++;
}
if (need_save) {
save_calls_db_state();
}
}
*/
G()->td_db()->get_message_db_async()->delete_all_dialog_messages(dialog_id, max_message_id,
Auto()); // TODO Promise
}
class MessagesManager::DeleteMessageLogEvent {
public:
LogEvent::Id id_{0};
FullMessageId full_message_id_;
std::vector<FileId> file_ids_;
template <class StorerT>
void store(StorerT &storer) const {
bool has_file_ids = !file_ids_.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(has_file_ids);
END_STORE_FLAGS();
td::store(full_message_id_, storer);
if (has_file_ids) {
td::store(file_ids_, storer);
}
}
template <class ParserT>
void parse(ParserT &parser) {
bool has_file_ids;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_file_ids);
END_PARSE_FLAGS();
td::parse(full_message_id_, parser);
if (has_file_ids) {
td::parse(file_ids_, parser);
}
}
};
void MessagesManager::delete_message_files(DialogId dialog_id, const Message *m) const {
for (auto file_id : get_message_file_ids(m)) {
if (need_delete_file({dialog_id, m->message_id}, file_id)) {
send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise<Unit>(), "delete_message_files");
}
}
}
bool MessagesManager::need_delete_file(FullMessageId full_message_id, FileId file_id) const {
if (being_readded_message_id_ == full_message_id || td_->auth_manager_->is_bot()) {
return false;
}
auto main_file_id = td_->file_manager_->get_file_view(file_id).get_main_file_id();
auto full_message_ids = td_->file_reference_manager_->get_some_message_file_sources(main_file_id);
LOG(INFO) << "Receive " << full_message_ids << " as sources for file " << main_file_id << "/" << file_id << " from "
<< full_message_id;
for (const auto &other_full_messsage_id : full_message_ids) {
if (other_full_messsage_id != full_message_id) {
return false;
}
}
return true;
}
bool MessagesManager::need_delete_message_files(DialogId dialog_id, const Message *m) const {
if (m == nullptr || td_->auth_manager_->is_bot()) {
return false;
}
auto dialog_type = dialog_id.get_type();
if (!m->message_id.is_scheduled() && !m->message_id.is_server() && dialog_type != DialogType::SecretChat) {
return false;
}
if (being_readded_message_id_ == FullMessageId{dialog_id, m->message_id}) {
return false;
}
if (m->forward_info != nullptr && m->forward_info->from_dialog_id.is_valid() &&
m->forward_info->from_message_id.is_valid()) {
// this function must not try to load the message, because it can be called from
// do_delete_message or add_scheduled_message_to_dialog
const Message *old_m = get_message({m->forward_info->from_dialog_id, m->forward_info->from_message_id});
if (old_m != nullptr && get_message_file_ids(old_m) == get_message_file_ids(m)) {
return false;
}
}
return true;
}
void MessagesManager::delete_message_from_database(Dialog *d, MessageId message_id, const Message *m,
bool is_permanently_deleted) {
CHECK(d != nullptr);
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return;
}
if (message_id.is_yet_unsent()) {
return;
}
if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_local() &&
m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) {
// must not load the message from the database
Message *top_m = get_message(d, m->top_thread_message_id);
if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) {
auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(),
m->message_id);
if (it != top_m->local_thread_message_ids.end() && *it == m->message_id) {
top_m->local_thread_message_ids.erase(it);
on_message_changed(d, top_m, false, "delete_message_from_database");
}
}
}
if (is_permanently_deleted) {
if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
} else {
// don't store failed to send message identifiers for bots to reduce memory usage
if (m == nullptr || !td_->auth_manager_->is_bot() || !m->is_failed_to_send) {
d->deleted_message_ids.insert(message_id);
send_closure_later(actor_id(this),
&MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages, d->dialog_id,
message_id);
}
}
if (message_id.is_any_server()) {
auto old_message_id = find_old_message_id(d->dialog_id, message_id);
if (old_message_id.is_valid()) {
bool have_old_message = get_message(d, old_message_id) != nullptr;
LOG(WARNING) << "Sent " << FullMessageId{d->dialog_id, message_id}
<< " was deleted before it was received. Have old " << old_message_id << " = " << have_old_message;
send_closure_later(actor_id(this), &MessagesManager::delete_messages, d->dialog_id,
vector<MessageId>{old_message_id}, false, Promise<Unit>());
delete_update_message_id(d->dialog_id, message_id);
}
}
}
if (m != nullptr && m->notification_id.is_valid()) {
CHECK(!message_id.is_scheduled());
auto from_mentions = is_from_mention_notification_group(m);
auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
if (group_info.group_id.is_valid()) {
if (group_info.last_notification_id == m->notification_id) {
// last notification is deleted, need to find new last notification
fix_dialog_last_notification_id(d, from_mentions, m->message_id);
}
if (is_message_notification_active(d, m)) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id,
m->notification_id, true, false, Promise<Unit>(), "delete_message_from_database");
}
}
} else if (!message_id.is_scheduled() && message_id > d->last_new_message_id) {
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
d->message_notification_group.group_id, message_id, false, "delete_message_from_database");
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
d->mention_notification_group.group_id, message_id, false, "delete_message_from_database");
}
auto need_delete_files = need_delete_message_files(d->dialog_id, m);
if (need_delete_files) {
delete_message_files(d->dialog_id, m);
}
if (!G()->parameters().use_message_db) {
return;
}
DeleteMessageLogEvent log_event;
log_event.full_message_id_ = {d->dialog_id, message_id};
if (need_delete_files) {
log_event.file_ids_ = get_message_file_ids(m);
}
do_delete_message_log_event(log_event);
}
void MessagesManager::do_delete_message_log_event(const DeleteMessageLogEvent &log_event) const {
CHECK(G()->parameters().use_message_db);
Promise<Unit> db_promise;
if (!log_event.file_ids_.empty()) {
auto log_event_id = log_event.id_;
if (log_event_id == 0) {
log_event_id =
binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessage, get_log_event_storer(log_event));
}
MultiPromiseActorSafe mpas{"DeleteMessageMultiPromiseActor"};
mpas.add_promise(
PromiseCreator::lambda([log_event_id, context_weak_ptr = get_context_weak_ptr()](Result<Unit> result) {
auto context = context_weak_ptr.lock();
if (result.is_error() || context == nullptr) {
return;
}
CHECK(context->get_id() == Global::ID);
auto global = static_cast<Global *>(context.get());
if (global->close_flag()) {
return;
}
binlog_erase(global->td_db()->get_binlog(), log_event_id);
}));
auto lock = mpas.get_promise();
for (auto file_id : log_event.file_ids_) {
if (need_delete_file(log_event.full_message_id_, file_id)) {
send_closure(G()->file_manager(), &FileManager::delete_file, file_id, mpas.get_promise(),
"do_delete_message_log_event");
}
}
db_promise = mpas.get_promise();
lock.set_value(Unit());
}
// message may not exist in the dialog
LOG(INFO) << "Delete " << log_event.full_message_id_ << " from database";
G()->td_db()->get_message_db_async()->delete_message(log_event.full_message_id_, std::move(db_promise));
}
void MessagesManager::attach_message_to_previous(Dialog *d, MessageId message_id, const char *source) {
CHECK(d != nullptr);
CHECK(message_id.is_valid());
MessagesIterator it(d, message_id);
Message *m = *it;
CHECK(m != nullptr);
CHECK(m->message_id == message_id);
LOG_CHECK(m->have_previous) << d->dialog_id << " " << message_id << " " << source;
--it;
LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source;
LOG(INFO) << "Attach " << message_id << " to the previous " << (*it)->message_id << " in " << d->dialog_id;
if ((*it)->have_next) {
m->have_next = true;
} else {
(*it)->have_next = true;
}
}
void MessagesManager::attach_message_to_next(Dialog *d, MessageId message_id, const char *source) {
CHECK(d != nullptr);
CHECK(message_id.is_valid());
MessagesIterator it(d, message_id);
Message *m = *it;
CHECK(m != nullptr);
CHECK(m->message_id == message_id);
LOG_CHECK(m->have_next) << d->dialog_id << " " << message_id << " " << source;
++it;
LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source;
LOG(INFO) << "Attach " << message_id << " to the next " << (*it)->message_id << " in " << d->dialog_id;
if ((*it)->have_previous) {
m->have_previous = true;
} else {
(*it)->have_previous = true;
}
}
bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message,
bool *need_update_dialog_pos, bool is_message_in_dialog) {
CHECK(d != nullptr);
CHECK(old_message != nullptr);
CHECK(new_message != nullptr);
LOG_CHECK(old_message->message_id == new_message->message_id)
<< d->dialog_id << ' ' << old_message->message_id << ' ' << new_message->message_id << ' '
<< is_message_in_dialog;
CHECK(old_message->random_y == new_message->random_y);
CHECK(need_update_dialog_pos != nullptr);
DialogId dialog_id = d->dialog_id;
MessageId message_id = old_message->message_id;
auto old_content_type = old_message->content->get_type();
auto new_content_type = new_message->content->get_type();
bool is_scheduled = message_id.is_scheduled();
bool need_send_update = false;
bool is_new_available =
new_content_type != MessageContentType::ChatDeleteHistory && new_message->restriction_reasons.empty();
bool replace_legacy = (old_message->legacy_layer != 0 &&
(new_message->legacy_layer == 0 || old_message->legacy_layer < new_message->legacy_layer)) ||
old_content_type == MessageContentType::Unsupported;
bool was_visible_message_reply_info = is_visible_message_reply_info(dialog_id, old_message);
if (old_message->date != new_message->date) {
if (new_message->date > 0) {
if (!(is_scheduled || message_id.is_yet_unsent() ||
(message_id.is_server() && message_id.get_server_message_id().get() == 1) ||
old_content_type == MessageContentType::ChannelMigrateFrom ||
old_content_type == MessageContentType::ChannelCreate)) {
LOG(ERROR) << "Date has changed for " << message_id << " in " << dialog_id << " from " << old_message->date
<< " to " << new_message->date << ", message content type is " << old_content_type << '/'
<< new_content_type;
}
CHECK(old_message->date > 0);
LOG(DEBUG) << "Message date has changed from " << old_message->date << " to " << new_message->date;
old_message->date = new_message->date;
if (!is_scheduled && d->last_message_id == message_id) {
*need_update_dialog_pos = true;
}
need_send_update = true;
} else {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with wrong date " << new_message->date
<< ", message content type is " << old_content_type << '/' << new_content_type;
}
}
if (old_message->date == old_message->edited_schedule_date) {
old_message->edited_schedule_date = 0;
}
bool is_edited = false;
int32 old_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date;
if (old_message->edit_date != new_message->edit_date) {
if (new_message->edit_date > 0) {
if (new_message->edit_date > old_message->edit_date) {
LOG(DEBUG) << "Message edit date has changed from " << old_message->edit_date << " to "
<< new_message->edit_date;
old_message->edit_date = new_message->edit_date;
}
} else {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " of type " << old_content_type << "/"
<< new_content_type << " with wrong edit date " << new_message->edit_date
<< ", old edit date = " << old_message->edit_date;
}
}
if (old_message->author_signature != new_message->author_signature) {
LOG(DEBUG) << "Author signature has changed for " << message_id << " in " << dialog_id << " sent by "
<< old_message->sender_user_id << "/" << new_message->sender_user_id << " or "
<< old_message->sender_dialog_id << "/" << new_message->sender_dialog_id << " from "
<< old_message->author_signature << " to " << new_message->author_signature;
old_message->author_signature = std::move(new_message->author_signature);
need_send_update = true;
}
if (old_message->sender_user_id != new_message->sender_user_id) {
// there can be race for sent signed posts or changed anonymous flag
LOG_IF(ERROR, old_message->sender_user_id != UserId() && new_message->sender_user_id != UserId())
<< message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_user_id << " to "
<< new_message->sender_user_id << ", message content type is " << old_content_type << '/' << new_content_type;
LOG_IF(WARNING, (new_message->sender_user_id.is_valid() || old_message->author_signature.empty()) &&
!old_message->sender_dialog_id.is_valid() && !new_message->sender_dialog_id.is_valid())
<< "Update message sender from " << old_message->sender_user_id << " to " << new_message->sender_user_id
<< " in " << dialog_id;
LOG(DEBUG) << "Change message sender";
old_message->sender_user_id = new_message->sender_user_id;
need_send_update = true;
}
if (old_message->sender_dialog_id != new_message->sender_dialog_id) {
// there can be race for changed anonymous flag
LOG_IF(ERROR, old_message->sender_dialog_id != DialogId() && new_message->sender_dialog_id != DialogId())
<< message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_dialog_id << " to "
<< new_message->sender_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type;
LOG(DEBUG) << "Change message sender";
old_message->sender_dialog_id = new_message->sender_dialog_id;
need_send_update = true;
}
if (old_message->forward_info == nullptr) {
if (new_message->forward_info != nullptr) {
if (!replace_legacy) {
LOG(ERROR) << message_id << " in " << dialog_id << " has received forward info " << *new_message->forward_info
<< ", really forwarded from " << old_message->real_forward_from_message_id << " in "
<< old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type
<< '/' << new_content_type;
} else {
LOG(DEBUG) << "Message forward has changed to " << *new_message->forward_info;
}
old_message->forward_info = std::move(new_message->forward_info);
need_send_update = true;
}
} else {
if (new_message->forward_info != nullptr) {
if (old_message->forward_info->author_signature != new_message->forward_info->author_signature) {
old_message->forward_info->author_signature = new_message->forward_info->author_signature;
LOG(DEBUG) << "Change message signature";
need_send_update = true;
}
if (*old_message->forward_info != *new_message->forward_info) {
bool need_warning = [&] {
if (replace_legacy) {
return false;
}
if (!is_scheduled && !message_id.is_yet_unsent()) {
return true;
}
return !is_forward_info_sender_hidden(new_message->forward_info.get()) &&
!is_forward_info_sender_hidden(old_message->forward_info.get());
}();
if (need_warning) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed forward info from "
<< *old_message->forward_info << " to " << *new_message->forward_info << ", really forwarded from "
<< old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id
<< ", message content type is " << old_content_type << '/' << new_content_type;
} else {
LOG(DEBUG) << "Message forward info has changed from " << *old_message->forward_info << " to "
<< *new_message->forward_info;
}
old_message->forward_info = std::move(new_message->forward_info);
need_send_update = true;
}
} else if (is_new_available) {
LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/"
<< old_message->sender_dialog_id << " has lost forward info " << *old_message->forward_info
<< ", really forwarded from " << old_message->real_forward_from_message_id << " in "
<< old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/'
<< new_content_type;
old_message->forward_info = nullptr;
need_send_update = true;
}
}
if (old_message->had_forward_info != new_message->had_forward_info) {
LOG(DEBUG) << "Message had_forward_info has changed from " << old_message->had_forward_info << " to "
<< new_message->had_forward_info;
old_message->had_forward_info = new_message->had_forward_info;
}
if (old_message->notification_id != new_message->notification_id) {
CHECK(!is_scheduled);
if (old_message->notification_id.is_valid()) {
if (new_message->notification_id.is_valid()) {
LOG(ERROR) << "Notification identifier for " << message_id << " in " << dialog_id
<< " has tried to change from " << old_message->notification_id << " to "
<< new_message->notification_id << ", message content type is " << old_content_type << '/'
<< new_content_type;
}
} else {
CHECK(new_message->notification_id.is_valid());
add_notification_id_to_message_id_correspondence(d, new_message->notification_id, message_id);
old_message->notification_id = new_message->notification_id;
}
}
if (new_message->is_mention_notification_disabled) {
old_message->is_mention_notification_disabled = true;
}
if (!new_message->from_database) {
old_message->from_database = false;
}
if (old_message->ttl_period != new_message->ttl_period) {
if (old_message->ttl_period != 0 || !message_id.is_yet_unsent()) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed TTL period from " << old_message->ttl_period
<< " to " << new_message->ttl_period;
} else {
LOG(DEBUG) << "Change message TTL period";
old_message->ttl_period = new_message->ttl_period;
need_send_update = true;
}
}
if (old_message->reply_to_message_id != new_message->reply_to_message_id) {
// Can't check "&& get_message_force(d, old_message->reply_to_message_id, "update_message") == nullptr", because it
// can change message tree and invalidate reference to old_message
if (new_message->reply_to_message_id == MessageId() || replace_legacy) {
LOG(DEBUG) << "Drop message reply_to_message_id";
unregister_message_reply(dialog_id, old_message);
old_message->reply_to_message_id = MessageId();
old_message->reply_in_dialog_id = DialogId();
update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog);
need_send_update = true;
} else if (is_new_available) {
if (message_id.is_yet_unsent() && old_message->reply_to_message_id == MessageId() &&
new_message->reply_in_dialog_id == DialogId() && is_deleted_message(d, new_message->reply_to_message_id) &&
get_message(d, new_message->reply_to_message_id) == nullptr && !is_message_in_dialog) {
LOG(INFO) << "Update replied message from " << old_message->reply_to_message_id << " to deleted "
<< new_message->reply_to_message_id;
old_message->reply_to_message_id = new_message->reply_to_message_id;
old_message->reply_in_dialog_id = DialogId();
update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog);
need_send_update = true;
} else if (old_message->reply_to_message_id.is_valid_scheduled() &&
old_message->reply_to_message_id.is_scheduled_server() &&
new_message->reply_to_message_id.is_valid_scheduled() &&
new_message->reply_to_message_id.is_scheduled_server() &&
old_message->reply_to_message_id.get_scheduled_server_message_id() ==
new_message->reply_to_message_id.get_scheduled_server_message_id() &&
new_message->reply_in_dialog_id == DialogId()) {
// schedule date has changed
old_message->reply_to_message_id = new_message->reply_to_message_id;
old_message->reply_in_dialog_id = DialogId();
need_send_update = true;
} else if (message_id.is_yet_unsent() && old_message->top_thread_message_id == new_message->reply_to_message_id &&
new_message->reply_in_dialog_id == DialogId()) {
LOG(INFO) << "Update replied message from " << old_message->reply_to_message_id << " to top thread "
<< new_message->reply_to_message_id;
unregister_message_reply(dialog_id, old_message);
old_message->reply_to_message_id = new_message->reply_to_message_id;
old_message->reply_in_dialog_id = DialogId();
register_message_reply(dialog_id, old_message);
need_send_update = true;
} else {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed it is replied message from "
<< old_message->reply_to_message_id << " to " << new_message->reply_to_message_id
<< ", message content type is " << old_content_type << '/' << new_content_type;
}
}
}
if (old_message->reply_in_dialog_id != new_message->reply_in_dialog_id) {
if (new_message->reply_in_dialog_id == DialogId() || replace_legacy) {
LOG(DEBUG) << "Drop message reply_in_dialog_id";
old_message->reply_in_dialog_id = DialogId();
need_send_update = true;
} else if (is_new_available && old_message->reply_in_dialog_id.is_valid()) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed dialog it is reply in from "
<< old_message->reply_in_dialog_id << " to " << new_message->reply_in_dialog_id
<< ", message content type is " << old_content_type << '/' << new_content_type;
}
}
if (old_message->top_thread_message_id != new_message->top_thread_message_id) {
if ((new_message->top_thread_message_id == MessageId() || old_message->top_thread_message_id == MessageId()) &&
(!is_message_in_dialog || replace_legacy)) {
LOG(DEBUG) << "Change message thread from " << old_message->top_thread_message_id << " to "
<< new_message->top_thread_message_id;
if (is_message_in_dialog && old_message->is_topic_message) {
if (old_message->top_thread_message_id != MessageId()) {
td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, old_message->top_thread_message_id, -1);
}
if (new_message->top_thread_message_id != MessageId()) {
td_->forum_topic_manager_->on_topic_message_count_changed(dialog_id, new_message->top_thread_message_id, +1);
}
}
old_message->top_thread_message_id = new_message->top_thread_message_id;
need_send_update = true;
} else if (is_new_available) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed message thread from "
<< old_message->top_thread_message_id << " to " << new_message->top_thread_message_id
<< ", message content type is " << old_content_type << '/' << new_content_type;
}
}
if (old_message->via_bot_user_id != new_message->via_bot_user_id) {
if ((!message_id.is_yet_unsent() || old_message->via_bot_user_id.is_valid()) && is_new_available &&
!replace_legacy) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed bot via it is sent from "
<< old_message->via_bot_user_id << " to " << new_message->via_bot_user_id
<< ", message content type is " << old_content_type << '/' << new_content_type;
}
LOG(DEBUG) << "Change message via_bot from " << old_message->via_bot_user_id << " to "
<< new_message->via_bot_user_id;
old_message->via_bot_user_id = new_message->via_bot_user_id;
need_send_update = true;
if (old_message->hide_via_bot && old_message->via_bot_user_id.is_valid()) {
// wrongly set hide_via_bot
old_message->hide_via_bot = false;
}
}
if (old_message->is_outgoing != new_message->is_outgoing && is_new_available) {
if (!replace_legacy && !(message_id.is_scheduled() && dialog_id == get_my_dialog_id())) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing
<< " to " << new_message->is_outgoing << ", message content type is " << old_content_type << '/'
<< new_content_type;
} else {
LOG(DEBUG) << "Message is_outgoing has changed from " << old_message->is_outgoing << " to "
<< new_message->is_outgoing;
}
old_message->is_outgoing = new_message->is_outgoing;
need_send_update = true;
}
LOG_IF(ERROR, old_message->is_channel_post != new_message->is_channel_post)
<< message_id << " in " << dialog_id << " has changed is_channel_post from " << old_message->is_channel_post
<< " to " << new_message->is_channel_post << ", message content type is " << old_content_type << '/'
<< new_content_type;
if (!old_message->top_thread_message_id.is_valid()) {
new_message->is_topic_message = false;
}
if (old_message->is_topic_message != new_message->is_topic_message &&
old_message->top_thread_message_id == new_message->top_thread_message_id) {
if (is_message_in_dialog) {
td_->forum_topic_manager_->on_topic_message_count_changed(
dialog_id, old_message->top_thread_message_id,
static_cast<int>(new_message->is_topic_message) - static_cast<int>(old_message->is_topic_message));
}
LOG_IF(ERROR, !message_id.is_yet_unsent() && !replace_legacy)
<< message_id << " in " << dialog_id << " has changed is_topic_message to " << new_message->is_topic_message;
old_message->is_topic_message = new_message->is_topic_message;
need_send_update = true;
}
if (old_message->contains_mention != new_message->contains_mention) {
if (old_message->edit_date == 0 && is_new_available && old_content_type != MessageContentType::PinMessage &&
old_content_type != MessageContentType::ExpiredPhoto && old_content_type != MessageContentType::ExpiredVideo &&
!replace_legacy) {
LOG(ERROR) << message_id << " in " << dialog_id << " has changed contains_mention from "
<< old_message->contains_mention << " to " << new_message->contains_mention
<< ", is_outgoing = " << old_message->is_outgoing << ", message content type is " << old_content_type
<< '/' << new_content_type;
}
// contains_mention flag shouldn't be changed, because the message will not be added to unread mention list
// and we are unable to show/hide message notification
// old_message->contains_mention = new_message->contains_mention;
// need_send_update = true;
}
if (old_message->disable_notification != new_message->disable_notification) {
LOG_IF(ERROR, old_message->edit_date == 0 && is_new_available && !replace_legacy)
<< "Disable_notification has changed from " << old_message->disable_notification << " to "
<< new_message->disable_notification
<< ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message"))
<< ". New message: " << to_string(get_message_object(dialog_id, new_message.get(), "update_message"));
// disable_notification flag shouldn't be changed, because we are unable to show/hide message notification
// old_message->disable_notification = new_message->disable_notification;
// need_send_update = true;
}
if (old_message->disable_web_page_preview != new_message->disable_web_page_preview) {
old_message->disable_web_page_preview = new_message->disable_web_page_preview;
}
if (old_message->noforwards != new_message->noforwards) {
LOG(DEBUG) << "Message can_be_saved has changed from " << !old_message->noforwards << " to "
<< !old_message->noforwards;
old_message->noforwards = new_message->noforwards;
need_send_update = true;
}
if (old_message->restriction_reasons != new_message->restriction_reasons) {
LOG(DEBUG) << "Message restriction_reasons have changed from " << old_message->restriction_reasons << " to "
<< old_message->restriction_reasons;
old_message->restriction_reasons = std::move(new_message->restriction_reasons);
need_send_update = true;
}
if (old_message->legacy_layer != new_message->legacy_layer) {
old_message->legacy_layer = new_message->legacy_layer;
}
if ((old_message->media_album_id == 0 || td_->auth_manager_->is_bot()) && new_message->media_album_id != 0) {
old_message->media_album_id = new_message->media_album_id;
LOG(DEBUG) << "Update message media_album_id";
need_send_update = true;
}
if (old_message->hide_edit_date != new_message->hide_edit_date) {
old_message->hide_edit_date = new_message->hide_edit_date;
}
int32 new_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date;
if (new_shown_edit_date != old_shown_edit_date) {
LOG(DEBUG) << "Message edit_date has changed";
is_edited = true;
need_send_update = true;
}
if (old_message->is_from_scheduled != new_message->is_from_scheduled) {
// is_from_scheduled flag shouldn't be changed, because we are unable to show/hide message notification
// old_message->is_from_scheduled = new_message->is_from_scheduled;
}
if (old_message->edit_date > 0) {
// inline keyboard can be edited
bool reply_markup_changed =
((old_message->reply_markup == nullptr) != (new_message->reply_markup == nullptr)) ||
(old_message->reply_markup != nullptr && *old_message->reply_markup != *new_message->reply_markup);
if (reply_markup_changed) {
if (d->reply_markup_message_id == message_id && !td_->auth_manager_->is_bot() &&
new_message->reply_markup == nullptr) {
set_dialog_reply_markup(d, MessageId());
}
LOG(DEBUG) << "Update message reply keyboard";
old_message->reply_markup = std::move(new_message->reply_markup);
is_edited = true;
need_send_update = true;
}
old_message->had_reply_markup = false;
} else {
if (old_message->reply_markup == nullptr) {
if (new_message->reply_markup != nullptr) {
// MessageGame and MessageInvoice reply markup can be generated server side
// some forwards retain their reply markup
if (old_content_type != MessageContentType::Game && old_content_type != MessageContentType::Invoice &&
need_message_changed_warning(old_message) && !replace_legacy) {
LOG(ERROR) << message_id << " in " << dialog_id << " has received reply markup " << *new_message->reply_markup
<< ", message content type is " << old_content_type << '/' << new_content_type;
} else {
LOG(DEBUG) << "Add message reply keyboard";
}
old_message->had_reply_markup = false;
old_message->reply_markup = std::move(new_message->reply_markup);
need_send_update = true;
}
} else {
if (new_message->reply_markup != nullptr) {
if (replace_legacy ||
(message_id.is_yet_unsent() && old_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard &&
new_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard)) {
// allow the server to update inline keyboard for sent messages
// this is needed to get correct button_id for UrlAuth buttons
old_message->had_reply_markup = false;
old_message->reply_markup = std::move(new_message->reply_markup);
need_send_update = true;
} else if (need_message_changed_warning(old_message) && is_new_available) {
LOG_IF(WARNING, *old_message->reply_markup != *new_message->reply_markup)
<< message_id << " in " << dialog_id << " has changed reply_markup from " << *old_message->reply_markup
<< " to " << *new_message->reply_markup;
}
} else {
// if the message is not accessible anymore, then we don't need a warning
if (need_message_changed_warning(old_message) && is_new_available) {
LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/"
<< old_message->sender_dialog_id << " has lost reply markup " << *old_message->reply_markup
<< ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message"))
<< ". New message: "
<< to_string(get_message_object(dialog_id, new_message.get(), "update_message"));
}
}
}
}
if (old_message->last_access_date < new_message->last_access_date) {
old_message->last_access_date = new_message->last_access_date;
}
if (old_message->history_generation < new_message->history_generation) {
old_message->history_generation = new_message->history_generation;
}
if (new_message->is_update_sent || is_edited) {
old_message->is_update_sent = true;
}
if (!is_scheduled && update_message_is_pinned(d, old_message, new_message->is_pinned, "update_message")) {
need_send_update = true;
}
if (!is_scheduled &&
update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) {
need_send_update = true;
}
// update_message_interaction_info must be called after top_thread_message_id is updated
if (update_message_interaction_info(d, old_message, new_message->view_count, new_message->forward_count, true,
std::move(new_message->reply_info), true, std::move(new_message->reactions),
"update_message")) {
need_send_update = true;
}
if (!is_scheduled) {
CHECK(!new_message->have_previous || !new_message->have_next);
if (new_message->have_previous && !old_message->have_previous) {
old_message->have_previous = true;
attach_message_to_previous(d, message_id, "update_message");
} else if (new_message->have_next && !old_message->have_next) {
old_message->have_next = true;
attach_message_to_next(d, message_id, "update_message");
}
}
bool is_content_changed = false;
if (update_message_content(dialog_id, old_message, std::move(new_message->content),
message_id.is_yet_unsent() && new_message->edit_date == 0, is_message_in_dialog,
is_content_changed)) {
send_update_message_content(d, old_message, is_message_in_dialog, "update_message");
need_send_update = true;
}
if (was_visible_message_reply_info && !is_visible_message_reply_info(dialog_id, old_message)) {
send_update_message_interaction_info(dialog_id, old_message);
}
if (is_edited && !td_->auth_manager_->is_bot()) {
send_update_message_edited(dialog_id, old_message);
}
if (!was_visible_message_reply_info && is_visible_message_reply_info(dialog_id, old_message)) {
send_update_message_interaction_info(dialog_id, old_message);
}
on_message_changed(d, old_message, need_send_update, "update_message");
return need_send_update;
}
bool MessagesManager::need_message_changed_warning(const Message *old_message) {
if (old_message->edit_date > 0) {
// message was edited
return false;
}
if (old_message->message_id.is_yet_unsent() &&
(old_message->forward_info != nullptr || old_message->had_forward_info ||
old_message->real_forward_from_dialog_id.is_valid())) {
// original message may be edited
return false;
}
if (old_message->ttl > 0) {
// message can expire
return false;
}
if (!old_message->restriction_reasons.empty()) {
return false;
}
return true;
}
bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_message,
unique_ptr<MessageContent> new_content, bool need_merge_files,
bool is_message_in_dialog, bool &is_content_changed) {
is_content_changed = false;
bool need_update = false;
unique_ptr<MessageContent> &old_content = old_message->content;
MessageContentType old_content_type = old_content->get_type();
MessageContentType new_content_type = new_content->get_type();
auto old_file_id = get_message_content_any_file_id(old_content.get());
bool need_finish_upload = old_file_id.is_valid() && need_merge_files;
if (old_content_type != new_content_type) {
if (old_message->ttl > 0 && old_message->ttl_expires_at > 0 &&
((new_content_type == MessageContentType::ExpiredPhoto && old_content_type == MessageContentType::Photo) ||
(new_content_type == MessageContentType::ExpiredVideo && old_content_type == MessageContentType::Video))) {
LOG(INFO) << "Do not apply expired message content early";
} else {
need_update = true;
LOG(INFO) << "Message content has changed its type from " << old_content_type << " to " << new_content_type;
old_message->is_content_secret = is_secret_message_content(old_message->ttl, new_content->get_type());
}
if (need_merge_files && old_file_id.is_valid()) {
auto new_file_id = get_message_content_any_file_id(new_content.get());
if (new_file_id.is_valid()) {
FileView old_file_view = td_->file_manager_->get_file_view(old_file_id);
FileView new_file_view = td_->file_manager_->get_file_view(new_file_id);
// if file type has changed, but file size remains the same, we are trying to update local location of the new
// file with the old local location
if (old_file_view.has_local_location() && !new_file_view.has_local_location() && old_file_view.size() != 0 &&
old_file_view.size() == new_file_view.size()) {
auto old_file_type = old_file_view.get_type();
auto new_file_type = new_file_view.get_type();
if (is_document_file_type(old_file_type) && is_document_file_type(new_file_type)) {
auto &old_location = old_file_view.local_location();
auto r_file_id = td_->file_manager_->register_local(
FullLocalFileLocation(new_file_type, old_location.path_, old_location.mtime_nsec_), dialog_id,
old_file_view.size());
if (r_file_id.is_ok()) {
LOG_STATUS(td_->file_manager_->merge(new_file_id, r_file_id.ok()));
}
}
}
}
}
} else {
merge_message_contents(td_, old_content.get(), new_content.get(), need_message_changed_warning(old_message),
dialog_id, need_merge_files, is_content_changed, need_update);
}
if (need_finish_upload) {
// the file is likely to be already merged with a server file, but if not we need to
// cancel file upload of the main file to allow next upload with the same file to succeed
cancel_upload_file(old_file_id, "update_message_content");
}
if (is_content_changed || need_update) {
if (is_message_in_dialog) {
reregister_message_content(td_, old_content.get(), new_content.get(), {dialog_id, old_message->message_id},
"update_message_content");
}
old_content = std::move(new_content);
old_message->last_edit_pts = 0;
update_message_content_file_id_remote(old_content.get(), old_file_id);
} else {
update_message_content_file_id_remote(old_content.get(), get_message_content_any_file_id(new_content.get()));
}
if (is_content_changed && !need_update) {
LOG(INFO) << "Content of " << old_message->message_id << " in " << dialog_id << " has changed";
}
if (need_update) {
auto file_ids = get_message_content_file_ids(old_content.get(), td_);
if (!file_ids.empty()) {
auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, old_message->message_id));
if (file_source_id.is_valid()) {
auto search_text = get_message_search_text(old_message);
for (auto file_id : file_ids) {
auto file_view = td_->file_manager_->get_file_view(file_id);
send_closure(td_->download_manager_actor_, &DownloadManager::change_search_text, file_view.get_main_file_id(),
file_source_id, search_text);
}
}
}
}
return need_update;
}
MessagesManager::Dialog *MessagesManager::get_dialog_by_message_id(MessageId message_id) {
CHECK(message_id.is_valid() && message_id.is_server());
auto dialog_id = message_id_to_dialog_id_.get(message_id);
if (dialog_id == DialogId()) {
if (G()->parameters().use_message_db) {
auto r_value =
G()->td_db()->get_message_db_sync()->get_message_by_unique_message_id(message_id.get_server_message_id());
if (r_value.is_ok()) {
Message *m = on_get_message_from_database(r_value.ok(), false, "get_dialog_by_message_id");
if (m != nullptr) {
dialog_id = r_value.ok().dialog_id;
CHECK(m->message_id == message_id);
LOG_CHECK(message_id_to_dialog_id_.get(message_id) == dialog_id)
<< message_id << ' ' << dialog_id << ' ' << message_id_to_dialog_id_.get(message_id) << ' '
<< m->debug_source;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
return d;
}
}
}
LOG(INFO) << "Can't find the chat by " << message_id;
return nullptr;
}
return get_dialog(dialog_id);
}
MessageId MessagesManager::get_message_id_by_random_id(Dialog *d, int64 random_id, const char *source) {
CHECK(d != nullptr);
if (random_id == 0) {
return MessageId();
}
auto it = d->random_id_to_message_id.find(random_id);
if (it == d->random_id_to_message_id.end()) {
if (G()->parameters().use_message_db && d->dialog_id.get_type() == DialogType::SecretChat) {
auto r_value = G()->td_db()->get_message_db_sync()->get_message_by_random_id(d->dialog_id, random_id);
if (r_value.is_ok()) {
debug_add_message_to_dialog_fail_reason_ = "not called";
Message *m = on_get_message_from_database(d, r_value.ok(), false, "get_message_id_by_random_id");
if (m != nullptr) {
LOG_CHECK(m->random_id == random_id)
<< random_id << " " << m->random_id << " " << d->random_id_to_message_id[random_id] << " "
<< d->random_id_to_message_id[m->random_id] << " " << m->message_id << " " << source << " "
<< m->from_database << get_message(d, m->message_id) << " " << m << " "
<< debug_add_message_to_dialog_fail_reason_;
LOG_CHECK(d->random_id_to_message_id.count(random_id))
<< source << " " << random_id << " " << m->message_id << " " << m->is_failed_to_send << " "
<< m->is_outgoing << " " << m->from_database << " " << get_message(d, m->message_id) << " " << m << " "
<< debug_add_message_to_dialog_fail_reason_;
LOG_CHECK(d->random_id_to_message_id[random_id] == m->message_id)
<< source << " " << random_id << " " << d->random_id_to_message_id[random_id] << " " << m->message_id
<< " " << m->is_failed_to_send << " " << m->is_outgoing << " " << m->from_database << " "
<< get_message(d, m->message_id) << " " << m << " " << debug_add_message_to_dialog_fail_reason_;
LOG(INFO) << "Found " << FullMessageId{d->dialog_id, m->message_id} << " by random_id " << random_id
<< " from " << source;
return m->message_id;
}
}
}
LOG(INFO) << "Found no message by random_id " << random_id << " from " << source;
return MessageId();
}
LOG(INFO) << "Found " << FullMessageId{d->dialog_id, it->second} << " by random_id " << random_id << " from "
<< source;
return it->second;
}
void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source, bool expect_no_access,
bool force_update_dialog_pos) {
LOG_CHECK(dialog_id.is_valid()) << source;
LOG_CHECK(is_inited_) << dialog_id << ' ' << source << ' ' << expect_no_access << ' ' << force_update_dialog_pos;
Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
LOG(INFO) << "Force create " << dialog_id << " from " << source;
if (loaded_dialogs_.count(dialog_id) > 0) {
LOG(INFO) << "Skip creation of " << dialog_id << ", because it is being loaded now";
return;
}
d = add_dialog(dialog_id, source);
update_dialog_pos(d, source);
if (dialog_id.get_type() == DialogType::SecretChat && !d->notification_settings.is_synchronized &&
td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) {
// secret chat is being created
// let's copy notification settings from main chat if available
VLOG(notifications) << "Create new secret " << dialog_id << " from " << source;
auto secret_chat_id = dialog_id.get_secret_chat_id();
{
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
Dialog *user_d = get_dialog_force(DialogId(user_id), source);
if (user_d != nullptr && user_d->notification_settings.is_synchronized) {
VLOG(notifications) << "Copy notification settings from " << user_d->dialog_id << " to " << dialog_id;
auto user_settings = &user_d->notification_settings;
auto new_notification_settings = DialogNotificationSettings(
user_settings->use_default_mute_until, user_settings->mute_until,
dup_notification_sound(user_settings->sound), true /*use_default_show_preview*/, false /*show_preview*/,
user_settings->silent_send_message, true, false, true, false);
new_notification_settings.is_secret_chat_show_preview_fixed = true;
update_dialog_notification_settings(dialog_id, &d->notification_settings,
std::move(new_notification_settings));
} else {
d->notification_settings.is_synchronized = true;
}
}
if (G()->parameters().use_message_db && !td_->auth_manager_->is_bot() &&
!td_->contacts_manager_->get_secret_chat_is_outbound(secret_chat_id)) {
auto notification_group_id = get_dialog_notification_group_id(dialog_id, d->message_notification_group);
if (notification_group_id.is_valid()) {
if (d->new_secret_chat_notification_id.is_valid()) {
LOG(ERROR) << "Found previously created " << d->new_secret_chat_notification_id << " in " << d->dialog_id
<< ", when creating it from " << source;
} else {
d->new_secret_chat_notification_id = get_next_notification_id(d, notification_group_id, MessageId());
if (d->new_secret_chat_notification_id.is_valid()) {
auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id);
bool is_changed = set_dialog_last_notification(dialog_id, d->message_notification_group, date,
d->new_secret_chat_notification_id, "add_new_secret_chat");
CHECK(is_changed);
VLOG(notifications) << "Create " << d->new_secret_chat_notification_id << " with " << secret_chat_id;
auto ringtone_id = get_dialog_notification_ringtone_id(dialog_id, d);
send_closure_later(G()->notification_manager(), &NotificationManager::add_notification,
notification_group_id, NotificationGroupType::SecretChat, dialog_id, date, dialog_id,
false, ringtone_id, 0, d->new_secret_chat_notification_id,
create_new_secret_chat_notification(), "add_new_secret_chat_notification");
}
}
}
}
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (!have_dialog_info(dialog_id)) {
if (expect_no_access && dialog_id.get_type() == DialogType::Channel &&
td_->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) {
LOG(INFO) << "Created " << dialog_id << " for min-channel from " << source;
} else {
LOG(ERROR) << "Forced to create unknown " << dialog_id << " from " << source;
}
} else if (!expect_no_access) {
LOG(ERROR) << "Have no access to " << dialog_id << " received from " << source << ", but forced to create it";
}
}
} else if (force_update_dialog_pos) {
update_dialog_pos(d, "force update dialog pos");
}
}
MessagesManager::Dialog *MessagesManager::add_dialog(DialogId dialog_id, const char *source) {
LOG(DEBUG) << "Creating " << dialog_id << " from " << source;
CHECK(!have_dialog(dialog_id));
LOG_CHECK(dialog_id.is_valid()) << source;
if (G()->parameters().use_message_db) {
// TODO preload dialog asynchronously, remove loading from this function
auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id);
if (r_value.is_ok()) {
LOG(INFO) << "Synchronously loaded " << dialog_id << " from database from " << source;
return add_new_dialog(parse_dialog(dialog_id, r_value.ok(), source), true, source);
}
}
auto dialog = make_unique<Dialog>();
dialog->dialog_id = dialog_id;
invalidate_message_indexes(dialog.get());
return add_new_dialog(std::move(dialog), false, source);
}
MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&dialog, bool is_loaded_from_database,
const char *source) {
Dialog *d = dialog.get();
auto dialog_id = d->dialog_id;
LOG_CHECK(is_inited_) << dialog_id << ' ' << is_loaded_from_database << ' ' << source;
LOG_CHECK(!have_dialog(dialog_id)) << dialog_id << ' ' << is_loaded_from_database << ' ' << source;
switch (dialog_id.get_type()) {
case DialogType::User:
if (dialog_id == get_my_dialog_id() && d->last_read_inbox_message_id == MessageId::max() &&
d->last_read_outbox_message_id == MessageId::max()) {
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;
d->is_available_reactions_inited = true;
break;
case DialogType::Chat:
d->is_is_blocked_inited = true;
break;
case DialogType::Channel: {
if (td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) {
d->last_read_outbox_message_id = MessageId::max();
d->is_last_read_outbox_message_id_inited = true;
}
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
d->pts = pts;
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, pts, "add_new_dialog");
}
}
break;
}
case DialogType::SecretChat:
if (d->last_new_message_id.get() <= MessageId::min().get()) {
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);
}
if (!d->notification_settings.is_secret_chat_show_preview_fixed) {
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(dialog_id, "fix secret chat show preview");
}
d->have_full_history = true;
d->have_full_history_source = 4;
d->need_restore_reply_markup = false;
d->is_last_read_inbox_message_id_inited = true;
d->is_last_read_outbox_message_id_inited = true;
d->is_last_pinned_message_id_inited = true;
d->is_theme_name_inited = true;
d->is_is_blocked_inited = true;
if (!d->is_folder_id_inited && !td_->auth_manager_->is_bot()) {
do_set_dialog_folder_id(
d, td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id()));
}
d->message_ttl = MessageTtl(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()));
d->is_message_ttl_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;
d->is_available_reactions_inited = true;
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (!is_loaded_from_database) {
on_dialog_updated(dialog_id, "add_new_dialog");
}
if (td_->auth_manager_->is_bot()) {
d->notification_settings.is_synchronized = true;
}
if (is_channel_difference_finished_.erase(dialog_id)) {
d->is_channel_difference_finished = true;
}
unique_ptr<Message> last_database_message = std::move(d->messages);
MessageId last_database_message_id = d->last_database_message_id;
d->last_database_message_id = MessageId();
int64 order = d->order;
d->order = DEFAULT_ORDER;
int32 last_clear_history_date = d->last_clear_history_date;
MessageId last_clear_history_message_id = d->last_clear_history_message_id;
d->last_clear_history_date = 0;
d->last_clear_history_message_id = MessageId();
DialogId default_join_group_call_as_dialog_id = d->default_join_group_call_as_dialog_id;
if (default_join_group_call_as_dialog_id != dialog_id &&
default_join_group_call_as_dialog_id.get_type() != DialogType::User &&
!have_dialog(default_join_group_call_as_dialog_id)) {
d->default_join_group_call_as_dialog_id = DialogId();
}
DialogId default_send_message_as_dialog_id = d->default_send_message_as_dialog_id;
bool need_drop_default_send_message_as_dialog_id = d->need_drop_default_send_message_as_dialog_id;
if (default_send_message_as_dialog_id != dialog_id &&
default_send_message_as_dialog_id.get_type() != DialogType::User &&
!have_dialog(default_send_message_as_dialog_id)) {
d->need_drop_default_send_message_as_dialog_id = false;
d->default_send_message_as_dialog_id = DialogId();
}
if (d->message_notification_group.group_id.is_valid()) {
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, dialog_id);
}
if (pending_dialog_group_call_updates_.count(dialog_id) > 0) {
auto it = pending_dialog_group_call_updates_.find(dialog_id);
bool has_active_group_call = it->second.first;
bool is_group_call_empty = it->second.second;
pending_dialog_group_call_updates_.erase(it);
if (d->has_active_group_call != has_active_group_call || d->is_group_call_empty != is_group_call_empty) {
if (!has_active_group_call) {
d->active_group_call_id = InputGroupCallId();
}
d->has_active_group_call = has_active_group_call;
d->is_group_call_empty = is_group_call_empty;
on_dialog_updated(dialog_id, "pending update_dialog_group_call");
}
}
fix_pending_join_requests(dialog_id, d->pending_join_request_count, d->pending_join_request_user_ids);
if (!is_loaded_from_database) {
CHECK(order == DEFAULT_ORDER);
CHECK(last_database_message == nullptr);
}
CHECK(!have_dialog(dialog_id));
dialogs_.set(dialog_id, std::move(dialog));
CHECK(!being_added_new_dialog_id_.is_valid());
being_added_new_dialog_id_ = dialog_id;
loaded_dialogs_.erase(dialog_id);
failed_to_load_dialogs_.erase(dialog_id);
fix_dialog_action_bar(d, d->action_bar.get());
send_update_new_chat(d);
being_added_new_dialog_id_ = DialogId();
LOG_CHECK(d->messages == nullptr) << d->messages->message_id << ' ' << d->last_message_id << ' '
<< d->last_database_message_id << ' '
<< d->debug_set_dialog_last_database_message_id << ' ' << d->messages->debug_source;
fix_new_dialog(d, std::move(last_database_message), last_database_message_id, order, last_clear_history_date,
last_clear_history_message_id, default_join_group_call_as_dialog_id, default_send_message_as_dialog_id,
need_drop_default_send_message_as_dialog_id, is_loaded_from_database, source);
return d;
}
void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_database_message,
MessageId last_database_message_id, int64 order, int32 last_clear_history_date,
MessageId last_clear_history_message_id,
DialogId default_join_group_call_as_dialog_id,
DialogId default_send_message_as_dialog_id,
bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database,
const char *source) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
auto dialog_type = dialog_id.get_type();
if (!td_->auth_manager_->is_bot() && dialog_type == DialogType::SecretChat) {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (user_id.is_valid()) {
force_create_dialog(DialogId(user_id), "add chat with user to load/store action_bar and is_blocked");
Dialog *user_d = get_dialog_force(DialogId(user_id), "fix_new_dialog");
if (user_d != nullptr && d->is_blocked != user_d->is_blocked) {
set_dialog_is_blocked(d, user_d->is_blocked);
}
}
}
if (d->is_empty && !d->have_full_history) {
LOG(ERROR) << "Drop invalid flag is_empty";
d->is_empty = false;
}
if (being_added_dialog_id_ != dialog_id && !td_->auth_manager_->is_bot() && !is_dialog_inited(d) &&
dialog_type != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read)) {
// asynchronously get dialog from the server
send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20");
}
if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get is_blocked from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init is_blocked");
} else if (being_added_dialog_id_ != dialog_id && !d->is_has_bots_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get has_bots from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init has_bots");
} else if (being_added_dialog_id_ != dialog_id && !d->is_theme_name_inited && !td_->auth_manager_->is_bot()) {
// asynchronously get dialog theme identifier from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init theme_name");
} else 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());
} else if (being_added_dialog_id_ != dialog_id && !d->is_folder_id_inited && !td_->auth_manager_->is_bot() &&
order != DEFAULT_ORDER) {
// asynchronously get dialog folder identifier from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init folder_id");
} else if (!d->is_message_ttl_inited && !td_->auth_manager_->is_bot() &&
have_input_peer(dialog_id, AccessRights::Write)) {
// asynchronously get dialog message TTL from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init message_ttl");
} else if (being_added_dialog_id_ != dialog_id && !d->is_available_reactions_inited &&
!td_->auth_manager_->is_bot()) {
// asynchronously get dialog available reactions from the server
reload_dialog_info_full(dialog_id, "fix_new_dialog init available_reactions");
}
if ((!d->know_action_bar || d->need_repair_action_bar) && !td_->auth_manager_->is_bot() &&
dialog_type != DialogType::SecretChat && dialog_id != get_my_dialog_id() &&
have_input_peer(dialog_id, AccessRights::Read)) {
// asynchronously get action bar from the server
reget_dialog_action_bar(dialog_id, "fix_new_dialog", false);
}
if (d->has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) {
repair_dialog_active_group_call_id(dialog_id);
}
if (d->notification_settings.is_synchronized && !d->notification_settings.is_use_default_fixed &&
have_input_peer(dialog_id, AccessRights::Read) && !td_->auth_manager_->is_bot()) {
LOG(INFO) << "Reget notification settings of " << dialog_id;
if (dialog_type == DialogType::SecretChat) {
d->notification_settings.is_use_default_fixed = true;
on_dialog_updated(dialog_id, "reget notification settings");
} else {
td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id, MessageId(),
Promise<Unit>());
}
}
if (td_->auth_manager_->is_bot() || d->notification_settings.use_default_mute_until ||
d->notification_settings.mute_until <= G()->unix_time()) {
d->notification_settings.mute_until = 0;
} else {
schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until);
}
if (d->pinned_message_notification_message_id.is_valid()) {
auto pinned_message_id = d->pinned_message_notification_message_id;
if (!d->mention_notification_group.group_id.is_valid()) {
LOG(ERROR) << "Have pinned message notification in " << pinned_message_id << " in " << dialog_id
<< ", but there is no mention notification group";
d->pinned_message_notification_message_id = MessageId();
on_dialog_updated(dialog_id, "fix pinned message notification");
} else if (is_dialog_pinned_message_notifications_disabled(d) ||
pinned_message_id <= d->last_read_inbox_message_id ||
pinned_message_id <= d->mention_notification_group.max_removed_message_id) {
VLOG(notifications) << "Remove disabled pinned message notification in " << pinned_message_id << " in "
<< dialog_id;
send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
d->mention_notification_group.group_id, pinned_message_id, true,
"fix pinned message notification");
d->pinned_message_notification_message_id = MessageId();
on_dialog_updated(dialog_id, "fix pinned message notification 2");
}
}
if (d->new_secret_chat_notification_id.is_valid()) {
auto &group_info = d->message_notification_group;
if (d->new_secret_chat_notification_id.get() <= group_info.max_removed_notification_id.get() ||
(group_info.last_notification_date == 0 && group_info.max_removed_notification_id.get() == 0)) {
VLOG(notifications) << "Fix removing new secret chat " << d->new_secret_chat_notification_id << " in "
<< dialog_id;
d->new_secret_chat_notification_id = NotificationId();
on_dialog_updated(dialog_id, "fix new secret chat notification identifier");
}
}
{
auto it = pending_add_dialog_last_database_message_dependent_dialogs_.find(dialog_id);
if (it != pending_add_dialog_last_database_message_dependent_dialogs_.end()) {
auto pending_dialog_ids = std::move(it->second);
pending_add_dialog_last_database_message_dependent_dialogs_.erase(it);
for (auto &pending_dialog_id : pending_dialog_ids) {
auto &counter_message = pending_add_dialog_last_database_message_[pending_dialog_id];
CHECK(counter_message.first > 0);
counter_message.first--;
if (counter_message.first == 0) {
LOG(INFO) << "Add postponed last database message in " << pending_dialog_id;
add_dialog_last_database_message(get_dialog(pending_dialog_id), std::move(counter_message.second));
pending_add_dialog_last_database_message_.erase(pending_dialog_id);
}
}
}
}
{
auto it = pending_add_default_join_group_call_as_dialog_id_.find(dialog_id);
if (it != pending_add_default_join_group_call_as_dialog_id_.end()) {
auto pending_dialog_ids = std::move(it->second);
pending_add_default_join_group_call_as_dialog_id_.erase(it);
for (auto &pending_dialog_id : pending_dialog_ids) {
on_update_dialog_default_join_group_call_as_dialog_id(pending_dialog_id, dialog_id, false);
}
}
}
{
auto it = pending_add_default_send_message_as_dialog_id_.find(dialog_id);
if (it != pending_add_default_send_message_as_dialog_id_.end()) {
auto pending_dialog_ids = std::move(it->second);
pending_add_default_send_message_as_dialog_id_.erase(it);
for (auto &pending_dialog_id : pending_dialog_ids) {
Dialog *pending_d = get_dialog(pending_dialog_id.first);
CHECK(pending_d != nullptr);
if (!pending_d->default_send_message_as_dialog_id.is_valid()) {
LOG(INFO) << "Set postponed message sender in " << pending_dialog_id << " to " << dialog_id;
pending_d->need_drop_default_send_message_as_dialog_id = pending_dialog_id.second;
pending_d->default_send_message_as_dialog_id = dialog_id;
send_update_chat_message_sender(pending_d);
}
}
}
}
if (dialog_id != being_added_dialog_id_) {
set_dialog_last_clear_history_date(d, last_clear_history_date, last_clear_history_message_id, "fix_new_dialog 8",
is_loaded_from_database);
set_dialog_order(d, order, false, is_loaded_from_database, "fix_new_dialog 9");
}
if (dialog_type != DialogType::SecretChat && d->last_new_message_id.is_valid() &&
!d->last_new_message_id.is_server()) {
// fix wrong last_new_message_id
d->last_new_message_id = d->last_new_message_id.get_prev_server_message_id();
on_dialog_updated(dialog_id, "fix_new_dialog 10");
}
bool need_get_history = true;
// add last database message to dialog
MessageId last_message_id;
if (last_database_message != nullptr) {
need_get_history = false;
last_message_id = last_database_message->message_id;
} else if (last_database_message_id.is_valid()) {
last_message_id = last_database_message_id;
}
if (!d->last_new_message_id.is_valid() && d->last_new_message_id != MessageId()) {
LOG(ERROR) << "Drop invalid last_new_message_id " << d->last_new_message_id << " in " << dialog_id;
d->last_new_message_id = MessageId();
}
if (last_message_id.is_valid()) {
if ((last_message_id.is_server() || dialog_type == DialogType::SecretChat) && !last_message_id.is_yet_unsent() &&
!d->last_new_message_id.is_valid()) {
LOG(ERROR) << "Bugfixing wrong last_new_message_id to " << last_message_id << " in " << dialog_id;
// must be called before set_dialog_first_database_message_id and set_dialog_last_database_message_id
set_dialog_last_new_message_id(d, last_message_id, "fix_new_dialog 1");
}
if (!d->first_database_message_id.is_valid() || d->first_database_message_id > last_message_id) {
LOG(ERROR) << "Bugfixing wrong first_database_message_id from " << d->first_database_message_id << " to "
<< last_message_id << " in " << dialog_id;
set_dialog_first_database_message_id(d, last_message_id, "fix_new_dialog 2");
}
set_dialog_last_database_message_id(d, last_message_id, "fix_new_dialog 3", is_loaded_from_database);
} else if (d->first_database_message_id.is_valid()) {
// ensure that first_database_message_id <= last_database_message_id
if (d->first_database_message_id <= d->last_new_message_id) {
set_dialog_last_database_message_id(d, d->last_new_message_id, "fix_new_dialog 4");
} else {
// can't fix last_database_message_id, drop first_database_message_id; it shouldn't happen anyway
set_dialog_first_database_message_id(d, MessageId(), "fix_new_dialog 5");
}
}
d->debug_first_database_message_id = d->first_database_message_id;
d->debug_last_database_message_id = d->last_database_message_id;
d->debug_last_new_message_id = d->last_new_message_id;
if (last_database_message != nullptr) {
Dependencies dependencies;
add_message_dependencies(dependencies, last_database_message.get());
int32 dependent_dialog_count = 0;
for (const auto &other_dialog_id : dependencies.get_dialog_ids()) {
if (other_dialog_id.is_valid() && !have_dialog(other_dialog_id)) {
LOG(INFO) << "Postpone adding of last message in " << dialog_id << " because of cyclic dependency with "
<< other_dialog_id;
pending_add_dialog_last_database_message_dependent_dialogs_[other_dialog_id].push_back(dialog_id);
dependent_dialog_count++;
}
};
auto last_message_date = last_database_message->date;
if (dependent_dialog_count == 0) {
if (!add_dialog_last_database_message(d, std::move(last_database_message))) {
// failed to add last message; keep the current position and get history from the database
d->pending_last_message_date = last_message_date;
d->pending_last_message_id = last_message_id;
}
} else {
// can't add message immediately, because need to notify first about adding of dependent dialogs
d->pending_last_message_date = last_message_date;
d->pending_last_message_id = last_message_id;
pending_add_dialog_last_database_message_[dialog_id] = {dependent_dialog_count, std::move(last_database_message)};
}
} else if (last_database_message_id.is_valid()) {
auto date = DialogDate(order, dialog_id).get_date();
if (date < MIN_PINNED_DIALOG_DATE) {
d->pending_last_message_date = date;
d->pending_last_message_id = last_message_id;
}
}
if (default_join_group_call_as_dialog_id != d->default_join_group_call_as_dialog_id) {
CHECK(default_join_group_call_as_dialog_id.is_valid());
CHECK(default_join_group_call_as_dialog_id.get_type() != DialogType::User);
CHECK(!d->default_join_group_call_as_dialog_id.is_valid());
if (!have_dialog(default_join_group_call_as_dialog_id)) {
LOG(INFO) << "Postpone adding of default join voice chat as " << default_join_group_call_as_dialog_id << " in "
<< dialog_id;
pending_add_default_join_group_call_as_dialog_id_[default_join_group_call_as_dialog_id].push_back(dialog_id);
} else {
on_update_dialog_default_join_group_call_as_dialog_id(dialog_id, default_join_group_call_as_dialog_id, false);
}
}
if (default_send_message_as_dialog_id != d->default_send_message_as_dialog_id) {
CHECK(default_send_message_as_dialog_id.is_valid());
CHECK(default_send_message_as_dialog_id.get_type() != DialogType::User);
CHECK(!d->default_send_message_as_dialog_id.is_valid());
if (!have_dialog(default_send_message_as_dialog_id)) {
LOG(INFO) << "Postpone setting of message sender " << default_send_message_as_dialog_id << " in " << dialog_id;
pending_add_default_send_message_as_dialog_id_[default_send_message_as_dialog_id].emplace_back(
dialog_id, need_drop_default_send_message_as_dialog_id);
} else {
LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_message_as_dialog_id;
d->need_drop_default_send_message_as_dialog_id = need_drop_default_send_message_as_dialog_id;
d->default_send_message_as_dialog_id = default_send_message_as_dialog_id;
send_update_chat_message_sender(d);
}
}
switch (dialog_type) {
case DialogType::User:
break;
case DialogType::Chat:
if (d->last_read_inbox_message_id < d->last_read_outbox_message_id) {
LOG(INFO) << "Last read outbox message is " << d->last_read_outbox_message_id << " in " << dialog_id
<< ", but last read inbox message is " << d->last_read_inbox_message_id;
// can't fix last_read_inbox_message_id by last_read_outbox_message_id because last_read_outbox_message_id is
// just a message identifier not less than an identifier of last read outgoing message and less than
// an identifier of first unread outgoing message, so it may not point to the outgoing message
// read_history_inbox(dialog_id, d->last_read_outbox_message_id, d->server_unread_count, "fix_new_dialog 6");
}
break;
case DialogType::Channel:
break;
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
}
if (d->delete_last_message_date != 0) {
if (d->last_message_id.is_valid()) {
LOG(ERROR) << "Last " << d->deleted_last_message_id << " in " << dialog_id << " was deleted at "
<< d->delete_last_message_date << ", but have last " << d->last_message_id;
d->delete_last_message_date = 0;
d->deleted_last_message_id = MessageId();
d->is_last_message_deleted_locally = false;
on_dialog_updated(dialog_id, "update_delete_last_message_date");
} else {
need_get_history = true;
}
}
if (!G()->parameters().use_message_db) {
d->has_loaded_scheduled_messages_from_database = true;
}
if (dialog_id != being_added_dialog_id_) {
update_dialog_pos(d, "fix_new_dialog 7", true, is_loaded_from_database);
}
if (is_loaded_from_database && d->order != order && order < MAX_ORDINARY_DIALOG_ORDER &&
!td_->contacts_manager_->is_dialog_info_received_from_server(dialog_id) && !d->had_last_yet_unsent_message &&
!td_->auth_manager_->is_bot()) {
LOG(ERROR) << dialog_id << " has order " << d->order << " instead of saved to database order " << order;
}
LOG(INFO) << "Loaded " << dialog_id << " with last new " << d->last_new_message_id << ", first database "
<< d->first_database_message_id << ", last database " << d->last_database_message_id << ", last "
<< d->last_message_id << " with order " << d->order;
VLOG(notifications) << "Have " << dialog_id << " with message " << d->message_notification_group.group_id
<< " with last " << d->message_notification_group.last_notification_id << " sent at "
<< d->message_notification_group.last_notification_date << ", max removed "
<< d->message_notification_group.max_removed_notification_id << "/"
<< d->message_notification_group.max_removed_message_id << " and new secret chat "
<< d->new_secret_chat_notification_id;
VLOG(notifications) << "Have " << dialog_id << " with mention " << d->mention_notification_group.group_id
<< " with last " << d->mention_notification_group.last_notification_id << " sent at "
<< d->mention_notification_group.last_notification_date << ", max removed "
<< d->mention_notification_group.max_removed_notification_id << "/"
<< d->mention_notification_group.max_removed_message_id << " and pinned "
<< d->pinned_message_notification_message_id;
VLOG(notifications) << "In " << dialog_id << " have last_read_inbox_message_id = " << d->last_read_inbox_message_id
<< ", last_new_message_id = " << d->last_new_message_id
<< ", max_notification_message_id = " << d->max_notification_message_id;
if (d->messages != nullptr) {
if (d->messages->message_id != last_message_id || d->messages->left != nullptr || d->messages->right != nullptr) {
auto common_data =
PSTRING() << ' ' << last_message_id << ' ' << d->last_message_id << ' ' << d->last_database_message_id << ' '
<< d->debug_set_dialog_last_database_message_id << ' ' << d->messages->debug_source << ' '
<< is_loaded_from_database << ' ' << source << ' ' << being_added_dialog_id_ << ' '
<< being_added_new_dialog_id_ << ' ' << dialog_id << ' ' << d->is_channel_difference_finished << ' '
<< debug_last_get_channel_difference_dialog_id_ << ' ' << debug_last_get_channel_difference_source_
<< ' ' << G()->parameters().use_message_db;
LOG_CHECK(d->messages->message_id == last_message_id) << d->messages->message_id << common_data;
LOG_CHECK(d->messages->left == nullptr)
<< d->messages->left->message_id << ' ' << d->messages->message_id << ' ' << d->messages->left->message_id
<< ' ' << d->messages->left->debug_source << common_data;
LOG_CHECK(d->messages->right == nullptr)
<< d->messages->right->message_id << ' ' << d->messages->message_id << ' ' << d->messages->right->message_id
<< ' ' << d->messages->right->debug_source << common_data;
}
}
// must be after update_dialog_pos, because uses d->order
// must be after checks that dialog has at most one message, because read_history_inbox can load
// pinned message to remove its notification
if (d->pending_read_channel_inbox_pts != 0 && !td_->auth_manager_->is_bot() &&
have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) {
if (d->pts == d->pending_read_channel_inbox_pts) {
d->pending_read_channel_inbox_pts = 0;
read_history_inbox(dialog_id, d->pending_read_channel_inbox_max_message_id,
d->pending_read_channel_inbox_server_unread_count, "fix_new_dialog 12");
on_dialog_updated(dialog_id, "fix_new_dialog 13");
} else if (d->pts > d->pending_read_channel_inbox_pts) {
d->need_repair_channel_server_unread_count = true;
d->pending_read_channel_inbox_pts = 0;
on_dialog_updated(dialog_id, "fix_new_dialog 14");
} else {
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
} else {
d->pending_read_channel_inbox_pts = 0;
}
if (need_get_history && !td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ &&
dialog_id != being_added_by_new_message_dialog_id_ && have_input_peer(dialog_id, AccessRights::Read) &&
(d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
get_history_from_the_end_impl(d, true, false, Auto(), "fix_new_dialog");
}
if (d->need_repair_server_unread_count && need_unread_counter(d->order)) {
CHECK(dialog_type != DialogType::SecretChat);
repair_server_unread_count(dialog_id, d->server_unread_count, "fix_new_dialog");
}
if (d->need_repair_channel_server_unread_count) {
repair_channel_server_unread_count(d);
}
}
bool MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message) {
CHECK(d != nullptr);
CHECK(last_database_message != nullptr);
CHECK(last_database_message->left == nullptr);
CHECK(last_database_message->right == nullptr);
auto dialog_id = d->dialog_id;
auto message_id = last_database_message->message_id;
CHECK(message_id.is_valid());
LOG_CHECK(d->last_database_message_id == message_id)
<< message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id;
bool need_update_dialog_pos = false;
const Message *m = nullptr;
if (have_input_peer(dialog_id, AccessRights::Read)) {
bool need_update = false;
last_database_message->have_previous = false;
last_database_message->have_next = false;
last_database_message->from_database = true;
m = add_message_to_dialog(d, std::move(last_database_message), false, &need_update, &need_update_dialog_pos,
"add_dialog_last_database_message 1");
if (need_update_dialog_pos) {
LOG(ERROR) << "Need to update pos in " << dialog_id;
}
}
if (m != nullptr) {
set_dialog_last_message_id(d, m->message_id, "add_dialog_last_database_message 2", m);
send_update_chat_last_message(d, "add_dialog_last_database_message 3");
} else {
if (d->pending_last_message_date != 0) {
d->pending_last_message_date = 0;
d->pending_last_message_id = MessageId();
need_update_dialog_pos = true;
}
on_dialog_updated(dialog_id, "add_dialog_last_database_message 4"); // resave without last database message
if (!td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ &&
dialog_id != being_added_by_new_message_dialog_id_ && have_input_peer(dialog_id, AccessRights::Read) &&
(d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
get_history_from_the_end_impl(d, true, false, Auto(), "add_dialog_last_database_message 5");
}
}
if (need_update_dialog_pos) {
update_dialog_pos(d, "add_dialog_last_database_message 6");
}
return m != nullptr;
}
void MessagesManager::update_dialogs_hints(const Dialog *d) {
if (!td_->auth_manager_->is_bot() && d->order != DEFAULT_ORDER) {
dialogs_hints_.add(-d->dialog_id.get(), td_->contacts_manager_->get_dialog_search_text(d->dialog_id));
}
}
void MessagesManager::update_dialogs_hints_rating(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
if (d->order == DEFAULT_ORDER) {
LOG(INFO) << "Remove " << d->dialog_id << " from chats search";
dialogs_hints_.remove(-d->dialog_id.get());
} else {
LOG(INFO) << "Change position of " << d->dialog_id << " in chats search";
dialogs_hints_.set_rating(-d->dialog_id.get(), -get_dialog_base_order(d));
}
}
int64 MessagesManager::get_dialog_order(MessageId message_id, int32 message_date) {
CHECK(!message_id.is_scheduled());
return (static_cast<int64>(message_date) << 32) +
message_id.get_prev_server_message_id().get_server_message_id().get();
}
bool MessagesManager::is_dialog_sponsored(const Dialog *d) const {
return d->order == DEFAULT_ORDER && d->dialog_id == sponsored_dialog_id_;
}
int64 MessagesManager::get_dialog_base_order(const Dialog *d) const {
if (td_->auth_manager_->is_bot()) {
return 0; // to not call get_dialog_list
}
if (is_dialog_sponsored(d)) {
return SPONSORED_DIALOG_ORDER;
}
if (d->order == DEFAULT_ORDER) {
return 0;
}
auto pinned_order = get_dialog_pinned_order(DialogListId(FolderId::main()), d->dialog_id);
if (pinned_order != DEFAULT_ORDER) {
return pinned_order;
}
return d->order;
}
int64 MessagesManager::get_dialog_private_order(const DialogList *list, const Dialog *d) const {
if (list == nullptr || td_->auth_manager_->is_bot()) {
return 0;
}
if (is_dialog_sponsored(d) && list->dialog_list_id == DialogListId(FolderId::main())) {
return SPONSORED_DIALOG_ORDER;
}
if (d->order == DEFAULT_ORDER) {
return 0;
}
auto pinned_order = get_dialog_pinned_order(list, d->dialog_id);
if (pinned_order != DEFAULT_ORDER) {
return pinned_order;
}
return d->order;
}
td_api::object_ptr<td_api::chatPosition> MessagesManager::get_chat_position_object(DialogListId dialog_list_id,
const Dialog *d) const {
if (td_->auth_manager_->is_bot()) {
return nullptr;
}
auto *list = get_dialog_list(dialog_list_id);
if (list == nullptr) {
return nullptr;
}
auto position = get_dialog_position_in_list(list, d);
if (position.public_order == 0) {
return nullptr;
}
auto chat_source = position.is_sponsored ? sponsored_dialog_source_.get_chat_source_object() : nullptr;
return td_api::make_object<td_api::chatPosition>(dialog_list_id.get_chat_list_object(), position.public_order,
position.is_pinned, std::move(chat_source));
}
vector<td_api::object_ptr<td_api::chatPosition>> MessagesManager::get_chat_positions_object(const Dialog *d) const {
vector<td_api::object_ptr<td_api::chatPosition>> positions;
if (!td_->auth_manager_->is_bot()) {
for (auto dialog_list_id : get_dialog_list_ids(d)) {
auto position = get_chat_position_object(dialog_list_id, d);
if (position != nullptr) {
positions.push_back(std::move(position));
}
}
if (is_dialog_sponsored(d)) {
CHECK(positions.empty());
positions.push_back(get_chat_position_object(DialogListId(FolderId::main()), d));
}
}
return positions;
}
int64 MessagesManager::get_next_pinned_dialog_order() {
current_pinned_dialog_order_++;
LOG(INFO) << "Assign pinned_order = " << current_pinned_dialog_order_;
return current_pinned_dialog_order_;
}
bool MessagesManager::is_removed_from_dialog_list(const Dialog *d) const {
switch (d->dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
return !td_->contacts_manager_->get_chat_is_active(d->dialog_id.get_chat_id());
case DialogType::Channel:
return !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).is_member();
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
break;
}
return false;
}
void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need_send_update,
bool is_loaded_from_database) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG(INFO) << "Trying to update " << d->dialog_id << " order from " << source;
int64 new_order = DEFAULT_ORDER;
if (!is_removed_from_dialog_list(d)) {
if (d->last_message_id != MessageId()) {
auto m = get_message(d, d->last_message_id);
CHECK(m != nullptr);
LOG(INFO) << "Last message at " << m->date << " found";
int64 last_message_order = get_dialog_order(m->message_id, m->date);
if (last_message_order > new_order) {
new_order = last_message_order;
}
} else if (d->delete_last_message_date > 0) {
LOG(INFO) << "Deleted last " << d->deleted_last_message_id << " at " << d->delete_last_message_date << " found";
int64 delete_order = get_dialog_order(d->deleted_last_message_id, d->delete_last_message_date);
if (delete_order > new_order) {
new_order = delete_order;
}
} else if (d->last_clear_history_date > 0) {
LOG(INFO) << "Clear history at " << d->last_clear_history_date << " found";
int64 clear_order = get_dialog_order(d->last_clear_history_message_id, d->last_clear_history_date);
if (clear_order > new_order) {
new_order = clear_order;
}
}
if (d->pending_last_message_date > 0) {
LOG(INFO) << "Pending last " << d->pending_last_message_id << " at " << d->pending_last_message_date << " found";
int64 pending_order = get_dialog_order(d->pending_last_message_id, d->pending_last_message_date);
if (pending_order > new_order) {
new_order = pending_order;
}
}
if (d->draft_message != nullptr && can_send_message(d->dialog_id).is_ok()) {
LOG(INFO) << "Draft message at " << d->draft_message->date << " found";
int64 draft_order = get_dialog_order(MessageId(), d->draft_message->date);
if (draft_order > new_order) {
new_order = draft_order;
}
}
switch (d->dialog_id.get_type()) {
case DialogType::Chat: {
auto chat_id = d->dialog_id.get_chat_id();
auto date = td_->contacts_manager_->get_chat_date(chat_id);
LOG(INFO) << "Creation at " << date << " found";
int64 join_order = get_dialog_order(MessageId(), date);
if (join_order > new_order && td_->contacts_manager_->get_chat_status(chat_id).is_member()) {
new_order = join_order;
}
break;
}
case DialogType::Channel: {
auto date = td_->contacts_manager_->get_channel_date(d->dialog_id.get_channel_id());
LOG(INFO) << "Join at " << date << " found";
int64 join_order = get_dialog_order(MessageId(), date);
if (join_order > new_order) {
new_order = join_order;
}
break;
}
case DialogType::SecretChat: {
auto date = td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id());
if (date != 0 && !is_deleted_secret_chat(d)) {
LOG(INFO) << "Creation at " << date << " found";
int64 creation_order = get_dialog_order(MessageId(), date);
if (creation_order > new_order) {
new_order = creation_order;
}
}
break;
}
default:
break;
}
if (new_order == DEFAULT_ORDER && !d->is_empty) {
LOG(INFO) << "There are no known messages in the chat, just leave it where it is";
new_order = d->order;
}
}
if (set_dialog_order(d, new_order, need_send_update, is_loaded_from_database, source)) {
on_dialog_updated(d->dialog_id, "update_dialog_pos");
}
}
bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_send_update, bool is_loaded_from_database,
const char *source) {
if (td_->auth_manager_->is_bot()) {
return false;
}
CHECK(d != nullptr);
DialogId dialog_id = d->dialog_id;
DialogDate old_date(d->order, dialog_id);
DialogDate new_date(new_order, dialog_id);
if (old_date == new_date) {
LOG(INFO) << "Order of " << d->dialog_id << " from " << d->folder_id << " is still " << new_order << " from "
<< source;
} else {
LOG(INFO) << "Update order of " << dialog_id << " from " << d->folder_id << " from " << d->order << " to "
<< new_order << " from " << source;
}
auto folder_ptr = get_dialog_folder(d->folder_id);
LOG_CHECK(folder_ptr != nullptr) << is_inited_ << ' ' << G()->close_flag() << ' ' << dialog_id << ' ' << d->folder_id
<< ' ' << is_loaded_from_database << ' ' << td_->auth_manager_->is_authorized()
<< ' ' << td_->auth_manager_->was_authorized() << ' ' << source;
auto &folder = *folder_ptr;
if (old_date == new_date) {
if (new_order == DEFAULT_ORDER) {
// first addition of a new left dialog
if (folder.ordered_dialogs_.insert(new_date).second) {
for (const auto &dialog_list : dialog_lists_) {
if (get_dialog_pinned_order(&dialog_list.second, d->dialog_id) != DEFAULT_ORDER) {
set_dialog_is_pinned(dialog_list.first, d, false);
}
}
}
}
return false;
}
auto dialog_positions = get_dialog_positions(d);
if (folder.ordered_dialogs_.erase(old_date) == 0) {
LOG_IF(ERROR, d->order != DEFAULT_ORDER) << dialog_id << " not found in the chat list from " << source;
}
folder.ordered_dialogs_.insert(new_date);
bool is_added = (d->order == DEFAULT_ORDER);
bool is_removed = (new_order == DEFAULT_ORDER);
d->order = new_order;
if (is_added) {
update_dialogs_hints(d);
}
update_dialogs_hints_rating(d);
update_dialog_lists(d, std::move(dialog_positions), need_send_update, is_loaded_from_database, source);
if (!is_loaded_from_database) {
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::Channel && is_added && being_added_dialog_id_ != dialog_id) {
repair_channel_server_unread_count(d);
LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
if (dialog_type == DialogType::Channel && is_removed) {
remove_all_dialog_notifications(d, false, source);
remove_all_dialog_notifications(d, true, source);
clear_active_dialog_actions(dialog_id);
}
}
return true;
}
void MessagesManager::update_dialog_lists(
Dialog *d, std::unordered_map<DialogListId, DialogPositionInList, DialogListIdHash> &&old_positions,
bool need_send_update, bool is_loaded_from_database, const char *source) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
if (being_added_dialog_id_ == dialog_id) {
// do not try to update dialog lists, while the dialog isn't inited
return;
}
LOG(INFO) << "Update lists of " << dialog_id << " from " << source;
if (d->order == DEFAULT_ORDER) {
for (auto &old_position : old_positions) {
if (old_position.second.is_pinned) {
set_dialog_is_pinned(old_position.first, d, false, false);
}
}
if (d->folder_id != FolderId::main()) {
LOG(INFO) << "Change folder of " << dialog_id << " to " << FolderId::main();
DialogDate dialog_date(d->order, dialog_id);
get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date);
do_set_dialog_folder_id(d, FolderId::main());
get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date);
}
}
for (auto &dialog_list : dialog_lists_) {
auto dialog_list_id = dialog_list.first;
auto &list = dialog_list.second;
const DialogPositionInList &old_position = old_positions[dialog_list_id];
const DialogPositionInList new_position = get_dialog_position_in_list(&list, d, true);
// sponsored chat is never "in list"
bool was_in_list = old_position.order != DEFAULT_ORDER && old_position.private_order != 0;
bool is_in_list = new_position.order != DEFAULT_ORDER && new_position.private_order != 0;
CHECK(was_in_list == is_dialog_in_list(d, dialog_list_id));
LOG(DEBUG) << "Update position of " << dialog_id << " in " << dialog_list_id << " from " << old_position << " to "
<< new_position;
bool need_update_unread_chat_count = false;
if (was_in_list != is_in_list) {
const int32 delta = was_in_list ? -1 : 1;
list.in_memory_dialog_total_count_ += delta;
if (!is_loaded_from_database) {
int32 &total_count = dialog_id.get_type() == DialogType::SecretChat ? list.secret_chat_total_count_
: list.server_dialog_total_count_;
if (total_count != -1) {
total_count += delta;
if (total_count < 0) {
LOG(ERROR) << "Total chat count in " << dialog_list_id << " became negative after removing " << dialog_id;
total_count = 0;
}
}
}
if (!is_loaded_from_database) {
need_update_unread_chat_count =
list.is_dialog_unread_count_inited_ && old_position.total_dialog_count != get_dialog_total_count(list);
auto unread_count = d->server_unread_count + d->local_unread_count;
const char *change_source = was_in_list ? "on_dialog_remove" : "on_dialog_add";
if (unread_count != 0 && list.is_message_unread_count_inited_) {
unread_count *= delta;
list.unread_message_total_count_ += unread_count;
if (is_dialog_muted(d)) {
list.unread_message_muted_count_ += unread_count;
}
send_update_unread_message_count(list, dialog_id, true, change_source);
}
if ((unread_count != 0 || d->is_marked_as_unread) && list.is_dialog_unread_count_inited_) {
list.unread_dialog_total_count_ += delta;
if (unread_count == 0 && d->is_marked_as_unread) {
list.unread_dialog_marked_count_ += delta;
}
if (is_dialog_muted(d)) {
list.unread_dialog_muted_count_ += delta;
if (unread_count == 0 && d->is_marked_as_unread) {
list.unread_dialog_muted_marked_count_ += delta;
}
}
need_update_unread_chat_count = true;
}
if (need_update_unread_chat_count) {
send_update_unread_chat_count(list, dialog_id, true, change_source);
}
}
if (was_in_list) {
remove_dialog_from_list(d, dialog_list_id);
} else {
add_dialog_to_list(d, dialog_list_id);
}
}
if (!need_update_unread_chat_count && list.is_dialog_unread_count_inited_ &&
old_position.total_dialog_count != get_dialog_total_count(list)) {
send_update_unread_chat_count(list, dialog_id, true, "changed total count");
}
if (need_send_update && need_send_update_chat_position(old_position, new_position)) {
send_update_chat_position(dialog_list_id, d, source);
}
if (!is_loaded_from_database && !old_position.is_sponsored && new_position.is_sponsored) {
// a chat is sponsored only if user isn't a chat member
remove_all_dialog_notifications(d, false, "update_dialog_lists 3");
remove_all_dialog_notifications(d, true, "update_dialog_lists 4");
}
}
}
void MessagesManager::update_last_dialog_date(FolderId folder_id) {
CHECK(!td_->auth_manager_->is_bot());
auto *folder = get_dialog_folder(folder_id);
CHECK(folder != nullptr);
auto old_last_dialog_date = folder->folder_last_dialog_date_;
folder->folder_last_dialog_date_ = folder->last_server_dialog_date_;
CHECK(old_last_dialog_date <= folder->folder_last_dialog_date_);
LOG(INFO) << "Update last dialog date in " << folder_id << " from " << old_last_dialog_date << " to "
<< folder->folder_last_dialog_date_;
LOG(INFO) << "Know about " << folder->ordered_dialogs_.size() << " chats";
if (old_last_dialog_date != folder->folder_last_dialog_date_) {
for (auto &dialog_list : dialog_lists_) {
update_list_last_pinned_dialog_date(dialog_list.second);
update_list_last_dialog_date(dialog_list.second);
}
}
if (G()->parameters().use_message_db &&
folder->last_database_server_dialog_date_ < folder->last_server_dialog_date_) {
auto last_server_dialog_date_string = PSTRING() << folder->last_server_dialog_date_.get_order() << ' '
<< folder->last_server_dialog_date_.get_dialog_id().get();
G()->td_db()->get_binlog_pmc()->set(PSTRING() << "last_server_dialog_date" << folder_id.get(),
last_server_dialog_date_string);
LOG(INFO) << "Save last server dialog date " << folder->last_server_dialog_date_;
folder->last_database_server_dialog_date_ = folder->last_server_dialog_date_;
folder->last_loaded_database_dialog_date_ = folder->last_server_dialog_date_;
}
}
// must not call get_dialog_filter
bool MessagesManager::do_update_list_last_pinned_dialog_date(DialogList &list) const {
CHECK(!td_->auth_manager_->is_bot());
if (list.last_pinned_dialog_date_ == MAX_DIALOG_DATE) {
return false;
}
if (!list.are_pinned_dialogs_inited_) {
return false;
}
DialogDate max_dialog_date = MIN_DIALOG_DATE;
for (const auto &pinned_dialog : list.pinned_dialogs_) {
if (!have_dialog(pinned_dialog.get_dialog_id())) {
break;
}
max_dialog_date = pinned_dialog;
}
if (list.pinned_dialogs_.empty() || max_dialog_date == list.pinned_dialogs_.back()) {
max_dialog_date = MAX_DIALOG_DATE;
}
if (list.last_pinned_dialog_date_ < max_dialog_date) {
LOG(INFO) << "Update last pinned dialog date in " << list.dialog_list_id << " from "
<< list.last_pinned_dialog_date_ << " to " << max_dialog_date;
list.last_pinned_dialog_date_ = max_dialog_date;
return true;
}
return false;
}
void MessagesManager::update_list_last_pinned_dialog_date(DialogList &list) {
CHECK(!td_->auth_manager_->is_bot());
if (do_update_list_last_pinned_dialog_date(list)) {
update_list_last_dialog_date(list);
}
}
// must not call get_dialog_filter
bool MessagesManager::do_update_list_last_dialog_date(DialogList &list, const vector<FolderId> &folder_ids) const {
CHECK(!td_->auth_manager_->is_bot());
auto new_last_dialog_date = list.last_pinned_dialog_date_;
for (auto folder_id : folder_ids) {
const auto &folder = *get_dialog_folder(folder_id);
if (folder.folder_last_dialog_date_ < new_last_dialog_date) {
new_last_dialog_date = folder.folder_last_dialog_date_;
}
}
if (list.list_last_dialog_date_ != new_last_dialog_date) {
auto old_last_dialog_date = list.list_last_dialog_date_;
LOG(INFO) << "Update last dialog date in " << list.dialog_list_id << " from " << old_last_dialog_date << " to "
<< new_last_dialog_date;
LOG_CHECK(old_last_dialog_date < new_last_dialog_date)
<< list.dialog_list_id << " " << old_last_dialog_date << " " << new_last_dialog_date << " "
<< get_dialog_list_folder_ids(list) << " " << list.last_pinned_dialog_date_ << " "
<< get_dialog_folder(FolderId::main())->folder_last_dialog_date_ << " "
<< get_dialog_folder(FolderId::archive())->folder_last_dialog_date_ << " " << list.load_list_queries_.size()
<< " " << list.pinned_dialogs_;
list.list_last_dialog_date_ = new_last_dialog_date;
return true;
}
return false;
}
void MessagesManager::update_list_last_dialog_date(DialogList &list) {
CHECK(!td_->auth_manager_->is_bot());
auto old_dialog_total_count = get_dialog_total_count(list);
auto old_last_dialog_date = list.list_last_dialog_date_;
if (!do_update_list_last_dialog_date(list, get_dialog_list_folder_ids(list))) {
LOG(INFO) << "Don't need to update last dialog date in " << list.dialog_list_id;
return;
}
for (auto it = std::upper_bound(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end(), old_last_dialog_date);
it != list.pinned_dialogs_.end() && *it <= list.list_last_dialog_date_; ++it) {
auto dialog_id = it->get_dialog_id();
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date");
}
bool is_list_further_loaded = list.list_last_dialog_date_ == MAX_DIALOG_DATE;
for (auto folder_id : get_dialog_list_folder_ids(list)) {
const auto &folder = *get_dialog_folder(folder_id);
for (auto it = folder.ordered_dialogs_.upper_bound(old_last_dialog_date);
it != folder.ordered_dialogs_.end() && *it <= folder.folder_last_dialog_date_; ++it) {
if (it->get_order() == DEFAULT_ORDER) {
break;
}
auto dialog_id = it->get_dialog_id();
if (get_dialog_pinned_order(&list, dialog_id) == DEFAULT_ORDER) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (is_dialog_in_list(d, list.dialog_list_id)) {
send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date 2");
is_list_further_loaded = true;
}
}
}
}
if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
recalc_unread_count(list.dialog_list_id, old_dialog_total_count, true);
}
LOG(INFO) << "After updating last dialog date in " << list.dialog_list_id << " to " << list.list_last_dialog_date_
<< " have is_list_further_loaded == " << is_list_further_loaded << " and " << list.load_list_queries_.size()
<< " pending load list queries";
if (is_list_further_loaded && !list.load_list_queries_.empty()) {
set_promises(list.load_list_queries_);
}
}
MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) {
return dialogs_.get_pointer(dialog_id);
}
const MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) const {
return dialogs_.get_pointer(dialog_id);
}
bool MessagesManager::have_dialog_force(DialogId dialog_id, const char *source) {
return loaded_dialogs_.count(dialog_id) > 0 || get_dialog_force(dialog_id, source) != nullptr;
}
MessagesManager::Dialog *MessagesManager::get_dialog_force(DialogId dialog_id, const char *source) {
init();
Dialog *d = get_dialog(dialog_id);
if (d != nullptr) {
LOG_CHECK(d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id;
return d;
}
if (!dialog_id.is_valid() || !G()->parameters().use_message_db || loaded_dialogs_.count(dialog_id) > 0 ||
failed_to_load_dialogs_.count(dialog_id) > 0) {
return nullptr;
}
auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id);
if (r_value.is_ok()) {
LOG(INFO) << "Loaded " << dialog_id << " from database from " << source;
d = on_load_dialog_from_database(dialog_id, r_value.move_as_ok(), source);
LOG_CHECK(d == nullptr || d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id;
} else {
failed_to_load_dialogs_.insert(dialog_id);
LOG(INFO) << "Failed to load " << dialog_id << " from database from " << source << ": "
<< r_value.error().message();
}
return d;
}
unique_ptr<MessagesManager::Dialog> MessagesManager::parse_dialog(DialogId dialog_id, const BufferSlice &value,
const char *source) {
LOG(INFO) << "Loaded " << dialog_id << " of size " << value.size() << " from database from " << source;
CHECK(dialog_id.is_valid());
auto dialog = make_unique<Dialog>();
Dialog *d = dialog.get();
d->dialog_id = dialog_id;
invalidate_message_indexes(d); // must initialize indexes, because some of them could be not parsed
loaded_dialogs_.insert(dialog_id);
auto status = log_event_parse(*d, value.as_slice());
if (status.is_error() || !d->dialog_id.is_valid() || d->dialog_id != dialog_id) {
// can't happen unless database is broken, but has been seen in the wild
// if dialog_id is invalid, we can't repair the dialog
LOG_CHECK(dialog_id.is_valid()) << "Can't repair " << dialog_id << ' ' << d->dialog_id << ' ' << status << ' '
<< source << ' ' << format::as_hex_dump<4>(value.as_slice());
LOG(ERROR) << "Repair broken " << dialog_id << ": " << status << ' ' << format::as_hex_dump<4>(value.as_slice());
// just clean all known data about the dialog
dialog = make_unique<Dialog>();
d = dialog.get();
d->dialog_id = dialog_id;
invalidate_message_indexes(d);
// and try to reget it from the server if possible
have_dialog_info_force(dialog_id);
if (have_input_peer(dialog_id, AccessRights::Read)) {
if (dialog_id.get_type() != DialogType::SecretChat) {
send_get_dialog_query(dialog_id, Auto(), 0, source);
}
} else {
LOG(ERROR) << "Can't repair unknown " << dialog_id << " from " << source;
}
}
CHECK(dialog_id == d->dialog_id);
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
if (d->default_join_group_call_as_dialog_id != dialog_id) {
dependencies.add_message_sender_dependencies(d->default_join_group_call_as_dialog_id);
}
if (d->default_send_message_as_dialog_id != dialog_id) {
dependencies.add_message_sender_dependencies(d->default_send_message_as_dialog_id);
}
if (d->messages != nullptr) {
add_message_dependencies(dependencies, d->messages.get());
}
if (d->draft_message != nullptr) {
add_formatted_text_dependencies(dependencies, &d->draft_message->input_message_text.text);
}
for (auto user_id : d->pending_join_request_user_ids) {
dependencies.add(user_id);
}
if (!dependencies.resolve_force(td_, source)) {
send_get_dialog_query(dialog_id, Auto(), 0, source);
}
auto dialog_type = d->dialog_id.get_type();
switch (dialog_type) {
case DialogType::Chat:
case DialogType::Channel:
if (get_active_reactions(d->available_reactions).empty() != ((d->available_reactions_generation & 1) == 1)) {
set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
}
break;
case DialogType::User:
case DialogType::SecretChat:
default:
break;
}
if (!d->need_drop_default_send_message_as_dialog_id && d->default_send_message_as_dialog_id.is_valid() &&
dialog_type == DialogType::Channel && !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id()) &&
!td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) {
LOG(INFO) << "Drop message sender in " << dialog_id;
d->need_drop_default_send_message_as_dialog_id = true;
}
return dialog;
}
MessagesManager::Dialog *MessagesManager::on_load_dialog_from_database(DialogId dialog_id, BufferSlice &&value,
const char *source) {
CHECK(G()->parameters().use_message_db);
if (!dialog_id.is_valid()) {
// hack
LogEventParser dialog_id_parser(value.as_slice());
int32 flags;
parse(flags, dialog_id_parser);
parse(dialog_id, dialog_id_parser);
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Failed to parse dialog_id from blob. Database is broken";
return nullptr;
}
}
auto old_d = get_dialog(dialog_id);
if (old_d != nullptr) {
return old_d;
}
LOG(INFO) << "Add new " << dialog_id << " from database from " << source;
return add_new_dialog(parse_dialog(dialog_id, value, source), true, source);
}
const DialogFilter *MessagesManager::get_server_dialog_filter(DialogFilterId dialog_filter_id) const {
CHECK(!disable_get_dialog_filter_);
for (const auto &filter : server_dialog_filters_) {
if (filter->dialog_filter_id == dialog_filter_id) {
return filter.get();
}
}
return nullptr;
}
DialogFilter *MessagesManager::get_dialog_filter(DialogFilterId dialog_filter_id) {
CHECK(!disable_get_dialog_filter_);
for (auto &filter : dialog_filters_) {
if (filter->dialog_filter_id == dialog_filter_id) {
return filter.get();
}
}
return nullptr;
}
const DialogFilter *MessagesManager::get_dialog_filter(DialogFilterId dialog_filter_id) const {
CHECK(!disable_get_dialog_filter_);
for (const auto &filter : dialog_filters_) {
if (filter->dialog_filter_id == dialog_filter_id) {
return filter.get();
}
}
return nullptr;
}
int32 MessagesManager::get_server_main_dialog_list_position() const {
int32 current_position = 0;
int32 current_server_position = 0;
if (current_position == main_dialog_list_position_) {
return current_server_position;
}
for (const auto &dialog_filter : dialog_filters_) {
current_position++;
if (!dialog_filter->is_empty(true)) {
current_server_position++;
}
if (current_position == main_dialog_list_position_) {
return current_server_position;
}
}
LOG(WARNING) << "Failed to find server position for " << main_dialog_list_position_ << " in chat filters";
return current_server_position;
}
vector<DialogFilterId> MessagesManager::get_dialog_filter_ids(const vector<unique_ptr<DialogFilter>> &dialog_filters,
int32 main_dialog_list_position) {
auto result = transform(dialog_filters, [](const auto &dialog_filter) { return dialog_filter->dialog_filter_id; });
if (static_cast<size_t>(main_dialog_list_position) <= result.size()) {
result.insert(result.begin() + main_dialog_list_position, DialogFilterId());
}
return result;
}
vector<FolderId> MessagesManager::get_dialog_filter_folder_ids(const DialogFilter *filter) {
CHECK(filter != nullptr);
if (filter->exclude_archived && filter->pinned_dialog_ids.empty() && filter->included_dialog_ids.empty()) {
return {FolderId::main()};
}
return {FolderId::main(), FolderId::archive()};
}
vector<FolderId> MessagesManager::get_dialog_list_folder_ids(const DialogList &list) const {
CHECK(!td_->auth_manager_->is_bot());
if (list.dialog_list_id.is_folder()) {
return {list.dialog_list_id.get_folder_id()};
}
if (list.dialog_list_id.is_filter()) {
auto dialog_filter_id = list.dialog_list_id.get_filter_id();
return get_dialog_filter_folder_ids(get_dialog_filter(dialog_filter_id));
}
UNREACHABLE();
return {};
}
bool MessagesManager::has_dialogs_from_folder(const DialogList &list, const DialogFolder &folder) const {
CHECK(!td_->auth_manager_->is_bot());
if (list.dialog_list_id.is_folder()) {
return list.dialog_list_id.get_folder_id() == folder.folder_id;
}
if (list.dialog_list_id.is_filter()) {
auto dialog_filter_id = list.dialog_list_id.get_filter_id();
auto *filter = get_dialog_filter(dialog_filter_id);
CHECK(filter != nullptr);
if (filter->exclude_archived && filter->pinned_dialog_ids.empty() && filter->included_dialog_ids.empty()) {
return folder.folder_id == FolderId::main();
}
return true;
}
UNREACHABLE();
return false;
}
bool MessagesManager::is_dialog_in_list(const Dialog *d, DialogListId dialog_list_id) {
return td::contains(d->dialog_list_ids, dialog_list_id);
}
void MessagesManager::add_dialog_to_list(Dialog *d, DialogListId dialog_list_id) {
LOG(INFO) << "Add " << d->dialog_id << " to " << dialog_list_id;
CHECK(!is_dialog_in_list(d, dialog_list_id));
d->dialog_list_ids.push_back(dialog_list_id);
}
void MessagesManager::remove_dialog_from_list(Dialog *d, DialogListId dialog_list_id) {
LOG(INFO) << "Remove " << d->dialog_id << " from " << dialog_list_id;
bool is_removed = td::remove(d->dialog_list_ids, dialog_list_id);
CHECK(is_removed);
}
bool MessagesManager::need_dialog_in_filter(const Dialog *d, const DialogFilter *filter) const {
CHECK(d != nullptr);
CHECK(filter != nullptr);
CHECK(d->order != DEFAULT_ORDER);
if (InputDialogId::contains(filter->pinned_dialog_ids, d->dialog_id)) {
return true;
}
if (InputDialogId::contains(filter->included_dialog_ids, d->dialog_id)) {
return true;
}
if (InputDialogId::contains(filter->excluded_dialog_ids, d->dialog_id)) {
return false;
}
if (d->dialog_id.get_type() == DialogType::SecretChat) {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
if (user_id.is_valid()) {
auto dialog_id = DialogId(user_id);
if (InputDialogId::contains(filter->pinned_dialog_ids, dialog_id)) {
return true;
}
if (InputDialogId::contains(filter->included_dialog_ids, dialog_id)) {
return true;
}
if (InputDialogId::contains(filter->excluded_dialog_ids, dialog_id)) {
return false;
}
}
}
if (d->unread_mention_count == 0 || is_dialog_mention_notifications_disabled(d)) {
if (filter->exclude_muted && is_dialog_muted(d)) {
return false;
}
if (filter->exclude_read && d->server_unread_count + d->local_unread_count == 0 && !d->is_marked_as_unread) {
return false;
}
}
if (filter->exclude_archived && d->folder_id == FolderId::archive()) {
return false;
}
switch (d->dialog_id.get_type()) {
case DialogType::User: {
auto user_id = d->dialog_id.get_user_id();
if (td_->contacts_manager_->is_user_bot(user_id)) {
return filter->include_bots;
}
if (user_id == td_->contacts_manager_->get_my_id() || td_->contacts_manager_->is_user_contact(user_id)) {
return filter->include_contacts;
}
return filter->include_non_contacts;
}
case DialogType::Chat:
return filter->include_groups;
case DialogType::Channel:
return is_broadcast_channel(d->dialog_id) ? filter->include_channels : filter->include_groups;
case DialogType::SecretChat: {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
if (td_->contacts_manager_->is_user_bot(user_id)) {
return filter->include_bots;
}
if (td_->contacts_manager_->is_user_contact(user_id)) {
return filter->include_contacts;
}
return filter->include_non_contacts;
}
default:
UNREACHABLE();
return false;
}
}
bool MessagesManager::need_dialog_in_list(const Dialog *d, const DialogList &list) const {
CHECK(!td_->auth_manager_->is_bot());
if (d->order == DEFAULT_ORDER) {
return false;
}
if (list.dialog_list_id.is_folder()) {
return d->folder_id == list.dialog_list_id.get_folder_id();
}
if (list.dialog_list_id.is_filter()) {
auto dialog_filter_id = list.dialog_list_id.get_filter_id();
return need_dialog_in_filter(d, get_dialog_filter(dialog_filter_id));
}
UNREACHABLE();
return false;
}
bool MessagesManager::need_send_update_chat_position(const DialogPositionInList &old_position,
const DialogPositionInList &new_position) {
if (old_position.public_order != new_position.public_order) {
return true;
}
if (old_position.public_order == 0) {
return false;
}
return old_position.is_pinned != new_position.is_pinned || old_position.is_sponsored != new_position.is_sponsored;
}
MessagesManager::DialogPositionInList MessagesManager::get_dialog_position_in_list(const DialogList *list,
const Dialog *d, bool actual) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(list != nullptr);
CHECK(d != nullptr);
DialogPositionInList position;
position.order = d->order;
if (is_dialog_sponsored(d) || (actual ? need_dialog_in_list(d, *list) : is_dialog_in_list(d, list->dialog_list_id))) {
position.private_order = get_dialog_private_order(list, d);
}
if (position.private_order != 0) {
position.public_order =
DialogDate(position.private_order, d->dialog_id) <= list->list_last_dialog_date_ ? position.private_order : 0;
position.is_pinned = get_dialog_pinned_order(list, d->dialog_id) != DEFAULT_ORDER;
position.is_sponsored = is_dialog_sponsored(d);
}
position.total_dialog_count = get_dialog_total_count(*list);
return position;
}
std::unordered_map<DialogListId, MessagesManager::DialogPositionInList, DialogListIdHash>
MessagesManager::get_dialog_positions(const Dialog *d) const {
CHECK(d != nullptr);
std::unordered_map<DialogListId, MessagesManager::DialogPositionInList, DialogListIdHash> positions;
if (!td_->auth_manager_->is_bot()) {
for (const auto &dialog_list : dialog_lists_) {
positions.emplace(dialog_list.first, get_dialog_position_in_list(&dialog_list.second, d));
}
}
return positions;
}
vector<DialogListId> MessagesManager::get_dialog_list_ids(const Dialog *d) {
return d->dialog_list_ids;
}
MessagesManager::DialogListView MessagesManager::get_dialog_lists(const Dialog *d) {
return DialogListView(this, get_dialog_list_ids(d));
}
MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialog_list_id) {
CHECK(!td_->auth_manager_->is_bot());
if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
dialog_list_id = DialogListId(FolderId::main());
}
if (dialog_lists_.count(dialog_list_id) == 0) {
LOG(INFO) << "Create " << dialog_list_id;
}
auto &list = dialog_lists_[dialog_list_id];
list.dialog_list_id = dialog_list_id;
return list;
}
MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) {
CHECK(!td_->auth_manager_->is_bot());
if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
dialog_list_id = DialogListId(FolderId::main());
}
auto it = dialog_lists_.find(dialog_list_id);
if (it == dialog_lists_.end()) {
return nullptr;
}
return &it->second;
}
const MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) const {
CHECK(!td_->auth_manager_->is_bot());
if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
dialog_list_id = DialogListId(FolderId::main());
}
auto it = dialog_lists_.find(dialog_list_id);
if (it == dialog_lists_.end()) {
return nullptr;
}
return &it->second;
}
MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) {
CHECK(!td_->auth_manager_->is_bot());
if (folder_id != FolderId::archive()) {
folder_id = FolderId::main();
}
auto it = dialog_folders_.find(folder_id);
if (it == dialog_folders_.end()) {
return nullptr;
}
return &it->second;
}
const MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) const {
CHECK(!td_->auth_manager_->is_bot());
if (folder_id != FolderId::archive()) {
folder_id = FolderId::main();
}
auto it = dialog_folders_.find(folder_id);
if (it == dialog_folders_.end()) {
return nullptr;
}
return &it->second;
}
string MessagesManager::get_channel_pts_key(DialogId dialog_id) {
CHECK(dialog_id.get_type() == DialogType::Channel);
auto channel_id = dialog_id.get_channel_id();
return PSTRING() << "ch.p" << channel_id.get();
}
int32 MessagesManager::load_channel_pts(DialogId dialog_id) const {
if (G()->ignore_background_updates() || !have_input_peer(dialog_id, AccessRights::Read)) {
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case
return 0;
}
auto pts = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get(get_channel_pts_key(dialog_id)));
LOG(INFO) << "Load " << dialog_id << " pts = " << pts;
return pts;
}
void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *source) {
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() == DialogType::Channel);
LOG_IF(ERROR, running_get_channel_difference(d->dialog_id))
<< "Set pts of " << d->dialog_id << " to " << new_pts << " from " << source
<< " while running getChannelDifference";
if (is_debug_message_op_enabled()) {
d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, new_pts, source);
}
// TODO delete_first_messages support in channels
if (new_pts == std::numeric_limits<int32>::max()) {
LOG(ERROR) << "Update " << d->dialog_id << " pts to -1 from " << source;
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(d->dialog_id));
d->pts = std::numeric_limits<int32>::max();
if (d->pending_read_channel_inbox_pts != 0) {
d->pending_read_channel_inbox_pts = 0;
}
return;
}
if (new_pts > d->pts || (0 < new_pts && new_pts < d->pts - 99999)) { // pts can only go up or drop cardinally
if (new_pts < d->pts - 99999) {
LOG(WARNING) << "Pts of " << d->dialog_id << " decreases from " << d->pts << " to " << new_pts << " from "
<< source;
} else {
LOG(INFO) << "Update " << d->dialog_id << " pts to " << new_pts << " from " << source;
}
d->pts = new_pts;
if (d->pending_read_channel_inbox_pts != 0 && d->pending_read_channel_inbox_pts <= d->pts) {
auto pts = d->pending_read_channel_inbox_pts;
d->pending_read_channel_inbox_pts = 0;
on_dialog_updated(d->dialog_id, "set_channel_pts");
if (d->pts == pts) {
read_history_inbox(d->dialog_id, d->pending_read_channel_inbox_max_message_id,
d->pending_read_channel_inbox_server_unread_count, "set_channel_pts");
} else if (d->pts > pts) {
repair_channel_server_unread_count(d);
}
}
if (!G()->ignore_background_updates() && have_input_peer(d->dialog_id, AccessRights::Read)) {
G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts));
}
} else if (new_pts < d->pts) {
LOG(ERROR) << "Receive wrong pts " << new_pts << " in " << d->dialog_id << " from " << source << ". Current pts is "
<< d->pts;
}
}
bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id,
const tl_object_ptr<telegram_api::Message> &message_ptr) {
if (dialog_id.get_type() != DialogType::Channel || !have_input_peer(dialog_id, AccessRights::Read) ||
dialog_id == debug_channel_difference_dialog_) {
return false;
}
if (message_ptr == nullptr) {
return true;
}
if (DialogId::get_message_dialog_id(message_ptr) != dialog_id) {
return false;
}
Dialog *d = get_dialog_force(dialog_id, "need_channel_difference_to_add_message");
if (d == nullptr) {
return load_channel_pts(dialog_id) > 0 && !is_channel_difference_finished_.count(dialog_id);
}
if (d->last_new_message_id == MessageId()) {
return d->pts > 0 && !d->is_channel_difference_finished;
}
return MessageId::get_message_id(message_ptr, false) > d->last_new_message_id;
}
void MessagesManager::run_after_channel_difference(DialogId dialog_id, Promise<Unit> &&promise) {
CHECK(dialog_id.get_type() == DialogType::Channel);
CHECK(have_input_peer(dialog_id, AccessRights::Read));
run_after_get_channel_difference_[dialog_id].push_back(std::move(promise));
const Dialog *d = get_dialog(dialog_id);
get_channel_difference(dialog_id, d == nullptr ? load_channel_pts(dialog_id) : d->pts, true,
"run_after_channel_difference");
}
bool MessagesManager::running_get_channel_difference(DialogId dialog_id) const {
return active_get_channel_differencies_.count(dialog_id) > 0;
}
void MessagesManager::on_channel_get_difference_timeout(DialogId dialog_id) {
if (G()->close_flag()) {
return;
}
CHECK(dialog_id.get_type() == DialogType::Channel);
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
get_channel_difference(dialog_id, d->pts, true, "on_channel_get_difference_timeout");
}
class MessagesManager::GetChannelDifferenceLogEvent {
public:
ChannelId channel_id;
int64 access_hash;
GetChannelDifferenceLogEvent() : channel_id(), access_hash() {
}
GetChannelDifferenceLogEvent(ChannelId channel_id, int64 access_hash)
: channel_id(channel_id), access_hash(access_hash) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(channel_id, storer);
td::store(access_hash, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(channel_id, parser);
td::parse(access_hash, parser);
}
};
void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source,
bool is_old) {
if (channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) {
LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because it is scheduled for later time";
return;
}
LOG_CHECK(dialog_id.get_type() == DialogType::Channel) << dialog_id << " " << source;
if (active_get_channel_differencies_.count(dialog_id)) {
LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because it has already been run";
return;
}
debug_last_get_channel_difference_dialog_id_ = dialog_id;
debug_last_get_channel_difference_source_ = source;
auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id());
if (input_channel == nullptr) {
LOG(ERROR) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because the channel is unknown";
after_get_channel_difference(dialog_id, false);
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because have no read access to it";
after_get_channel_difference(dialog_id, false);
return;
}
if (force && get_channel_difference_to_log_event_id_.count(dialog_id) == 0 && !G()->ignore_background_updates()) {
auto channel_id = dialog_id.get_channel_id();
CHECK(input_channel->get_id() == telegram_api::inputChannel::ID);
auto access_hash = static_cast<const telegram_api::inputChannel &>(*input_channel).access_hash_;
auto log_event = GetChannelDifferenceLogEvent(channel_id, access_hash);
auto log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::GetChannelDifference,
get_log_event_storer(log_event));
get_channel_difference_to_log_event_id_.emplace(dialog_id, log_event_id);
}
return do_get_channel_difference(dialog_id, pts, force, std::move(input_channel), is_old, source);
}
void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, bool force,
tl_object_ptr<telegram_api::InputChannel> &&input_channel, bool is_old,
const char *source) {
auto inserted = active_get_channel_differencies_.emplace(dialog_id, source);
if (!inserted.second) {
LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because it has already been run";
return;
}
// must work even we know nothing about the dialog
// can be called multiple times before after_get_channel_difference
const Dialog *d = get_dialog(dialog_id);
if (d != nullptr) {
if (d->message_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
d->message_notification_group.group_id);
}
if (d->mention_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
d->mention_notification_group.group_id);
}
}
int32 limit = td_->auth_manager_->is_bot() && !is_old ? MAX_BOT_CHANNEL_DIFFERENCE : MAX_CHANNEL_DIFFERENCE;
if (pts <= 0) {
pts = 1;
limit = MIN_CHANNEL_DIFFERENCE;
}
LOG(INFO) << "-----BEGIN GET CHANNEL DIFFERENCE----- for " << dialog_id << " with pts " << pts << " and limit "
<< limit << " from " << source;
td_->create_handler<GetChannelDifferenceQuery>()->send(dialog_id, std::move(input_channel), pts, limit, force);
}
void MessagesManager::process_get_channel_difference_updates(
DialogId dialog_id, int32 new_pts, vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
vector<tl_object_ptr<telegram_api::Update>> &&other_updates) {
LOG(INFO) << "In get channel difference for " << dialog_id << " receive " << new_messages.size() << " messages and "
<< other_updates.size() << " other updates";
CHECK(!debug_channel_difference_dialog_.is_valid());
debug_channel_difference_dialog_ = dialog_id;
// identifiers of edited and deleted messages
FlatHashSet<MessageId, MessageIdHash> changed_message_ids;
for (auto &update_ptr : other_updates) {
bool is_good_update = true;
switch (update_ptr->get_id()) {
case telegram_api::updateMessageID::ID: {
// in channels.getDifference updateMessageID can't be received for scheduled messages
auto sent_message_update = move_tl_object_as<telegram_api::updateMessageID>(update_ptr);
on_update_message_id(sent_message_update->random_id_, MessageId(ServerMessageId(sent_message_update->id_)),
"get_channel_difference");
update_ptr = nullptr;
break;
}
case telegram_api::updateDeleteChannelMessages::ID: {
auto *update = static_cast<const telegram_api::updateDeleteChannelMessages *>(update_ptr.get());
if (DialogId(ChannelId(update->channel_id_)) != dialog_id) {
is_good_update = false;
} else {
for (auto &message : update->messages_) {
changed_message_ids.insert(MessageId(ServerMessageId(message)));
}
}
break;
}
case telegram_api::updateEditChannelMessage::ID: {
auto *update = static_cast<const telegram_api::updateEditChannelMessage *>(update_ptr.get());
auto full_message_id = FullMessageId::get_full_message_id(update->message_, false);
if (full_message_id.get_dialog_id() != dialog_id) {
is_good_update = false;
} else {
changed_message_ids.insert(full_message_id.get_message_id());
}
break;
}
case telegram_api::updatePinnedChannelMessages::ID: {
auto *update = static_cast<const telegram_api::updatePinnedChannelMessages *>(update_ptr.get());
if (DialogId(ChannelId(update->channel_id_)) != dialog_id) {
is_good_update = false;
}
break;
}
default:
is_good_update = false;
break;
}
if (!is_good_update) {
LOG(ERROR) << "Receive wrong update in channelDifference of " << dialog_id << ": " << to_string(update_ptr);
update_ptr = nullptr;
}
}
auto is_edited_message = [](const tl_object_ptr<telegram_api::Message> &message) {
if (message->get_id() != telegram_api::message::ID) {
return false;
}
return static_cast<const telegram_api::message *>(message.get())->edit_date_ > 0;
};
for (auto &message : new_messages) {
if (is_edited_message(message)) {
auto message_id = MessageId::get_message_id(message, false);
if (message_id.is_valid()) {
changed_message_ids.insert(message_id);
}
}
}
// extract awaited sent messages, which were edited or deleted after that
auto postponed_updates_it = postponed_channel_updates_.find(dialog_id);
struct AwaitedMessage {
tl_object_ptr<telegram_api::Message> message;
Promise<Unit> promise;
};
std::map<MessageId, AwaitedMessage> awaited_messages;
if (postponed_updates_it != postponed_channel_updates_.end()) {
auto &updates = postponed_updates_it->second;
while (!updates.empty()) {
auto it = updates.begin();
auto update_pts = it->second.pts;
if (update_pts > new_pts) {
break;
}
auto update = std::move(it->second.update);
auto promise = std::move(it->second.promise);
updates.erase(it);
if (update->get_id() == telegram_api::updateNewChannelMessage::ID) {
auto update_new_channel_message = static_cast<telegram_api::updateNewChannelMessage *>(update.get());
auto message_id = MessageId::get_message_id(update_new_channel_message->message_, false);
FullMessageId full_message_id(dialog_id, message_id);
if (update_message_ids_.count(full_message_id) > 0 && changed_message_ids.count(message_id) > 0) {
changed_message_ids.erase(message_id);
AwaitedMessage awaited_message;
awaited_message.message = std::move(update_new_channel_message->message_);
awaited_message.promise = std::move(promise);
awaited_messages.emplace(message_id, std::move(awaited_message));
continue;
}
}
LOG(INFO) << "Skip to be applied from getChannelDifference " << to_string(update);
promise.set_value(Unit());
}
if (updates.empty()) {
postponed_channel_updates_.erase(postponed_updates_it);
}
}
// if last message is pretty old, we might have missed the update
bool need_repair_unread_count =
!new_messages.empty() && get_message_date(new_messages[0]) < G()->unix_time() - 2 * 86400;
auto it = awaited_messages.begin();
for (auto &message : new_messages) {
auto message_id = MessageId::get_message_id(message, false);
while (it != awaited_messages.end() && it->first < message_id) {
on_get_message(std::move(it->second.message), true, true, false, true, true, "postponed channel update");
it->second.promise.set_value(Unit());
++it;
}
Promise<Unit> promise;
if (it != awaited_messages.end() && it->first == message_id) {
if (is_edited_message(message)) {
// the new message is edited, apply postponed one and move this to updateEditChannelMessage
other_updates.push_back(make_tl_object<telegram_api::updateEditChannelMessage>(std::move(message), new_pts, 0));
message = std::move(it->second.message);
promise = std::move(it->second.promise);
} else {
it->second.promise.set_value(Unit());
}
++it;
}
on_get_message(std::move(message), true, true, false, true, true, "get channel difference");
promise.set_value(Unit());
}
while (it != awaited_messages.end()) {
on_get_message(std::move(it->second.message), true, true, false, true, true, "postponed channel update 2");
it->second.promise.set_value(Unit());
++it;
}
for (auto &update : other_updates) {
if (update != nullptr) {
process_channel_update(std::move(update));
}
}
LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
if (need_repair_unread_count) {
repair_channel_server_unread_count(get_dialog(dialog_id));
}
CHECK(debug_channel_difference_dialog_ == dialog_id);
debug_channel_difference_dialog_ = DialogId();
}
void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id,
MessageId read_inbox_max_message_id, int32 server_unread_count,
int32 unread_mention_count, int32 unread_reaction_count,
MessageId read_outbox_max_message_id,
vector<tl_object_ptr<telegram_api::Message>> &&messages) {
FlatHashMap<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
for (auto &message : messages) {
auto message_id = MessageId::get_message_id(message, false);
if (!message_id.is_valid()) {
continue;
}
auto message_dialog_id = DialogId::get_message_dialog_id(message);
if (!message_dialog_id.is_valid()) {
message_dialog_id = dialog_id;
}
auto full_message_id = FullMessageId(message_dialog_id, message_id);
full_message_id_to_message[full_message_id] = std::move(message);
}
FullMessageId last_full_message_id(dialog_id, last_message_id);
if (last_message_id.is_valid()) {
if (full_message_id_to_message.count(last_full_message_id) == 0) {
LOG(ERROR) << "Last " << last_message_id << " in " << dialog_id << " not found. Have:";
for (auto &message : full_message_id_to_message) {
LOG(ERROR) << to_string(message.second);
}
return;
}
}
CHECK(!last_message_id.is_scheduled());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
// There are many ways of handling a gap in a channel:
// 1) Delete all known messages in the chat, begin from scratch. It is easy to implement, but suddenly disappearing
// messages looks awful for the user.
// 2) Save all messages loaded in the memory until application restart, but delete all messages from the database.
// Messages left in the memory must be lazily updated using calls to getHistory. It looks much smoothly for the
// user, he will need to redownload messages only after client restart. Unsynchronized messages left in the
// memory shouldn't be saved to database, results of getHistory and getMessage must be used to update state of
// deleted and edited messages left in the memory.
// 3) Save all messages loaded in the memory and stored in the database without saving that some messages form
// continuous ranges. Messages in the database will be excluded from results of getChatHistory and
// searchChatMessages after application restart and will be available only through getMessage.
// Every message should still be checked using getHistory. It has more disadvantages over 2) than advantages.
// 4) Save all messages with saving all data about continuous message ranges. Messages from the database may be used
// as results of getChatHistory and (if implemented continuous ranges support for searching shared media)
// searchChatMessages. The messages should still be lazily checked using getHistory, but they are still available
// offline. It is the best way for gaps support, but it is pretty hard to implement correctly.
// It should be also noted that some messages like outgoing live location messages shouldn't be deleted.
if (last_message_id > d->last_new_message_id) {
// TODO properly support last_message_id <= d->last_new_message_id
set_dialog_first_database_message_id(d, MessageId(), "on_get_channel_dialog 6");
set_dialog_last_database_message_id(d, MessageId(), "on_get_channel_dialog 7");
d->have_full_history = false;
d->have_full_history_source = 0;
d->is_empty = false;
}
invalidate_message_indexes(d);
// d->history_generation++;
on_dialog_updated(dialog_id, "on_get_channel_dialog 10");
// TODO properly support last_message_id <= d->last_new_message_id
if (last_message_id > d->last_new_message_id) { // if last message is really a new message
if (!d->last_new_message_id.is_valid() && last_message_id <= d->max_added_message_id) {
auto prev_message_id = MessageId(ServerMessageId(last_message_id.get_server_message_id().get() - 1));
remove_dialog_newer_messages(d, prev_message_id, "on_get_channel_dialog 15");
}
d->last_new_message_id = MessageId();
set_dialog_last_message_id(d, MessageId(), "on_get_channel_dialog 20");
send_update_chat_last_message(d, "on_get_channel_dialog 30");
FullMessageId added_full_message_id;
if (last_full_message_id.get_message_id().is_valid()) {
last_full_message_id = on_get_message(std::move(full_message_id_to_message[last_full_message_id]), true, true,
false, true, true, "channel difference too long");
}
if (added_full_message_id.get_message_id().is_valid()) {
if (added_full_message_id.get_message_id() == d->last_new_message_id) {
CHECK(last_full_message_id == added_full_message_id);
CHECK(d->last_message_id == d->last_new_message_id);
} else {
LOG(ERROR) << added_full_message_id << " doesn't became last new message, which is " << d->last_new_message_id;
dump_debug_message_op(d, 2);
}
} else if (last_message_id > d->last_new_message_id) {
set_dialog_last_new_message_id(d, last_message_id,
"on_get_channel_dialog 40"); // skip updates about some messages
}
}
if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() &&
read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) {
read_inbox_max_message_id = d->last_read_inbox_message_id;
}
if (d->server_unread_count != server_unread_count || d->last_read_inbox_message_id != read_inbox_max_message_id) {
set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, server_unread_count, d->local_unread_count,
false, "on_get_channel_dialog 50");
}
if (d->unread_mention_count != unread_mention_count) {
set_dialog_unread_mention_count(d, unread_mention_count);
update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
if (d->unread_reaction_count != unread_reaction_count) {
set_dialog_unread_reaction_count(d, unread_reaction_count);
// update_dialog_mention_notification_count(d);
send_update_chat_unread_reaction_count(d, "on_get_channel_dialog 60");
}
if (d->last_read_outbox_message_id != read_outbox_max_message_id) {
set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id);
}
}
void MessagesManager::on_get_channel_difference(
DialogId dialog_id, int32 request_pts, int32 request_limit,
tl_object_ptr<telegram_api::updates_ChannelDifference> &&difference_ptr) {
LOG(INFO) << "----- END GET CHANNEL DIFFERENCE----- for " << dialog_id;
CHECK(active_get_channel_differencies_.count(dialog_id) == 1);
active_get_channel_differencies_.erase(dialog_id);
auto d = get_dialog_force(dialog_id, "on_get_channel_difference");
if (difference_ptr == nullptr) {
bool have_access = have_input_peer(dialog_id, AccessRights::Read);
if (have_access) {
if (d == nullptr) {
force_create_dialog(dialog_id, "on_get_channel_difference failed");
}
auto &delay = channel_get_difference_retry_timeouts_[dialog_id];
if (delay == 0) {
delay = 1;
}
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(),
Random::fast(delay * 1000, delay * 1500) * 1e-3);
delay *= 2;
if (delay > 60) {
delay = Random::fast(60, 80);
}
} else {
after_get_channel_difference(dialog_id, false);
}
return;
}
channel_get_difference_retry_timeouts_.erase(dialog_id);
LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with pts = " << request_pts
<< " and limit = " << request_limit << ": " << to_string(difference_ptr);
bool have_new_messages = false;
switch (difference_ptr->get_id()) {
case telegram_api::updates_channelDifferenceEmpty::ID:
if (d == nullptr) {
// no need to create the dialog
after_get_channel_difference(dialog_id, true);
return;
}
break;
case telegram_api::updates_channelDifference::ID: {
auto difference = static_cast<telegram_api::updates_channelDifference *>(difference_ptr.get());
have_new_messages = !difference->new_messages_.empty();
td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference");
td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference");
break;
}
case telegram_api::updates_channelDifferenceTooLong::ID: {
auto difference = static_cast<telegram_api::updates_channelDifferenceTooLong *>(difference_ptr.get());
have_new_messages = difference->dialog_->get_id() == telegram_api::dialog::ID && !difference->messages_.empty();
td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifferenceTooLong");
td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifferenceTooLong");
break;
}
default:
UNREACHABLE();
}
bool need_update_dialog_pos = false;
if (d == nullptr) {
if (have_new_messages) {
CHECK(!being_added_by_new_message_dialog_id_.is_valid());
being_added_by_new_message_dialog_id_ = dialog_id;
}
d = add_dialog(dialog_id, "on_get_channel_difference");
being_added_by_new_message_dialog_id_ = DialogId();
need_update_dialog_pos = true;
}
int32 cur_pts = d->pts <= 0 ? 1 : d->pts;
LOG_IF(ERROR, cur_pts != request_pts) << "Channel PTS has changed from " << request_pts << " to " << d->pts << " in "
<< dialog_id << " during getChannelDifference";
bool is_final = true;
bool is_old = false;
int32 timeout = 0;
switch (difference_ptr->get_id()) {
case telegram_api::updates_channelDifferenceEmpty::ID: {
auto difference = move_tl_object_as<telegram_api::updates_channelDifferenceEmpty>(difference_ptr);
int32 flags = difference->flags_;
is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0;
LOG_IF(ERROR, !is_final) << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = "
<< request_pts << " and limit = " << request_limit << " in " << dialog_id
<< ", but it is not final";
if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) {
timeout = difference->timeout_;
}
// bots can receive channelDifferenceEmpty with pts bigger than known pts
// also, this can happen for deleted channels
if (request_pts != difference->pts_ && !td_->auth_manager_->is_bot() &&
have_input_peer(dialog_id, AccessRights::Read)) {
LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed to "
<< difference->pts_;
}
set_channel_pts(d, difference->pts_, "channel difference empty");
break;
}
case telegram_api::updates_channelDifference::ID: {
auto difference = move_tl_object_as<telegram_api::updates_channelDifference>(difference_ptr);
int32 flags = difference->flags_;
is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0;
if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) {
timeout = difference->timeout_;
}
auto new_pts = difference->pts_;
if (request_pts >= new_pts && request_pts > 1 && (request_pts > new_pts || !td_->auth_manager_->is_bot())) {
LOG(ERROR) << "Receive channelDifference as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
new_pts = request_pts + 1;
}
if (difference->new_messages_.size() > 1) {
// check that new messages are received in increasing message_id order
MessageId cur_message_id;
for (const auto &message : difference->new_messages_) {
auto message_id = MessageId::get_message_id(message, false);
if (message_id <= cur_message_id) {
LOG(ERROR) << "Receive " << cur_message_id << " after " << message_id << " in channelDifference of "
<< dialog_id << " with pts " << request_pts << " and limit " << request_limit << ": "
<< to_string(difference);
after_get_channel_difference(dialog_id, false);
return;
}
cur_message_id = message_id;
}
}
if (!is_final && !difference->new_messages_.empty() && td_->auth_manager_->is_bot()) {
auto date = get_message_date(difference->new_messages_.back());
if (0 < date && date < G()->unix_time() - 2 * 86400) {
is_old = true;
}
}
process_get_channel_difference_updates(dialog_id, new_pts, std::move(difference->new_messages_),
std::move(difference->other_updates_));
set_channel_pts(d, new_pts, "channel difference");
break;
}
case telegram_api::updates_channelDifferenceTooLong::ID: {
auto difference = move_tl_object_as<telegram_api::updates_channelDifferenceTooLong>(difference_ptr);
telegram_api::dialog *dialog = nullptr;
switch (difference->dialog_->get_id()) {
case telegram_api::dialog::ID:
dialog = static_cast<telegram_api::dialog *>(difference->dialog_.get());
break;
case telegram_api::dialogFolder::ID:
return after_get_channel_difference(dialog_id, false);
default:
UNREACHABLE();
return;
}
CHECK(dialog != nullptr);
if ((dialog->flags_ & telegram_api::dialog::PTS_MASK) == 0) {
LOG(ERROR) << "Receive " << dialog_id << " without pts";
return after_get_channel_difference(dialog_id, false);
}
int32 flags = difference->flags_;
is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0;
if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) {
timeout = difference->timeout_;
}
auto new_pts = dialog->pts_;
if (request_pts > new_pts - request_limit) {
LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
if (request_pts >= new_pts) {
new_pts = request_pts + 1;
}
}
set_dialog_folder_id(d, FolderId(dialog->folder_id_));
on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_),
"updates.channelDifferenceTooLong");
bool is_marked_as_unread = dialog->unread_mark_;
if (is_marked_as_unread != d->is_marked_as_unread) {
set_dialog_is_marked_as_unread(d, is_marked_as_unread);
}
update_dialog_draft_message(d, get_draft_message(td_->contacts_manager_.get(), std::move(dialog->draft_)), true,
false);
on_get_channel_dialog(dialog_id, MessageId(ServerMessageId(dialog->top_message_)),
MessageId(ServerMessageId(dialog->read_inbox_max_id_)), dialog->unread_count_,
dialog->unread_mentions_count_, dialog->unread_reactions_count_,
MessageId(ServerMessageId(dialog->read_outbox_max_id_)), std::move(difference->messages_));
update_dialog_pos(d, "updates.channelDifferenceTooLong");
if (!td_->auth_manager_->is_bot()) {
// set is_pinned only after updating dialog pos to ensure that order is initialized
bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0;
bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id);
if (is_pinned != was_pinned) {
set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
}
}
set_channel_pts(d, new_pts, "channel difference too long");
break;
}
default:
UNREACHABLE();
}
if (need_update_dialog_pos) {
update_dialog_pos(d, "on_get_channel_difference");
}
if (!is_final) {
LOG_IF(ERROR, timeout > 0) << "Have timeout in nonfinal ChannelDifference in " << dialog_id;
get_channel_difference(dialog_id, d->pts, true, "on_get_channel_difference", is_old);
return;
}
LOG_IF(ERROR, timeout == 0) << "Have no timeout in final ChannelDifference in " << dialog_id;
if (timeout > 0 && d->is_opened) {
channel_get_difference_timeout_.add_timeout_in(dialog_id.get(), timeout);
}
after_get_channel_difference(dialog_id, true);
}
void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool success) {
LOG(INFO) << "After " << (success ? "" : "un") << "successful get channel difference in " << dialog_id;
LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
auto log_event_it = get_channel_difference_to_log_event_id_.find(dialog_id);
if (log_event_it != get_channel_difference_to_log_event_id_.end()) {
if (!G()->close_flag()) {
binlog_erase(G()->td_db()->get_binlog(), log_event_it->second);
}
get_channel_difference_to_log_event_id_.erase(log_event_it);
}
auto d = get_dialog(dialog_id);
bool have_access = have_input_peer(dialog_id, AccessRights::Read);
auto pts = d != nullptr ? d->pts : load_channel_pts(dialog_id);
auto postponed_updates_it = postponed_channel_updates_.find(dialog_id);
if (postponed_updates_it != postponed_channel_updates_.end()) {
auto &updates = postponed_updates_it->second;
LOG(INFO) << "Begin to apply " << updates.size() << " postponed channel updates";
while (!updates.empty()) {
auto it = updates.begin();
auto update = std::move(it->second.update);
auto update_pts = it->second.pts;
auto update_pts_count = it->second.pts_count;
auto promise = std::move(it->second.promise);
updates.erase(it);
auto old_size = updates.size();
auto update_id = update->get_id();
if (have_access) {
add_pending_channel_update(dialog_id, std::move(update), update_pts, update_pts_count, std::move(promise),
"apply postponed channel updates", true);
} else {
promise.set_value(Unit());
}
if (updates.size() != old_size || running_get_channel_difference(dialog_id)) {
if (success && update_pts - 10000 < pts && update_pts_count == 1) {
// if getChannelDifference was successful and update pts is near channel pts,
// we hope that the update eventually can be applied
LOG(INFO) << "Can't apply postponed channel updates";
} else {
// otherwise protect from getChannelDifference repeating calls by dropping postponed updates
LOG(WARNING) << "Failed to apply postponed updates of type " << update_id << " in " << dialog_id
<< " with pts " << pts << ", update pts is " << update_pts << ", update pts count is "
<< update_pts_count;
vector<Promise<Unit>> update_promises;
for (auto &postponed_update : updates) {
update_promises.push_back(std::move(postponed_update.second.promise));
}
updates.clear();
for (auto &update_promise : update_promises) {
update_promise.set_value(Unit());
}
}
break;
}
}
if (updates.empty()) {
postponed_channel_updates_.erase(postponed_updates_it);
}
LOG(INFO) << "Finish to apply postponed channel updates";
}
if (d != nullptr) {
d->is_channel_difference_finished = true;
if (d->message_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference,
d->message_notification_group.group_id);
}
if (d->mention_notification_group.group_id.is_valid()) {
send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference,
d->mention_notification_group.group_id);
}
} else {
is_channel_difference_finished_.insert(dialog_id);
}
if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) {
send_update_chat_read_inbox(d, true, "after_get_channel_difference");
}
auto promise_it = run_after_get_channel_difference_.find(dialog_id);
if (promise_it != run_after_get_channel_difference_.end()) {
auto promises = std::move(promise_it->second);
run_after_get_channel_difference_.erase(promise_it);
set_promises(promises);
}
auto it = pending_channel_on_get_dialogs_.find(dialog_id);
if (it != pending_channel_on_get_dialogs_.end()) {
LOG(INFO) << "Apply postponed results of channel getDialogs for " << dialog_id;
PendingOnGetDialogs res = std::move(it->second);
pending_channel_on_get_dialogs_.erase(it);
on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages),
std::move(res.promise));
}
if (d != nullptr && !td_->auth_manager_->is_bot() && have_access && !d->last_message_id.is_valid() && !d->is_empty &&
(d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
get_history_from_the_end_impl(d, true, false, Auto(), "after_get_channel_difference");
}
}
void MessagesManager::reget_message_from_server_if_needed(DialogId dialog_id, const Message *m) {
if (!m->message_id.is_any_server() || dialog_id.get_type() == DialogType::SecretChat) {
return;
}
if (need_reget_message_content(m->content.get()) || (m->legacy_layer != 0 && m->legacy_layer < MTPROTO_LAYER) ||
m->reply_info.need_reget(td_)) {
FullMessageId full_message_id{dialog_id, m->message_id};
LOG(INFO) << "Reget from server " << full_message_id;
get_message_from_server(full_message_id, Auto(), "reget_message_from_server_if_needed");
}
}
void MessagesManager::speculatively_update_active_group_call_id(Dialog *d, const Message *m) {
CHECK(m != nullptr);
if (!m->message_id.is_any_server() || m->content->get_type() != MessageContentType::GroupCall) {
return;
}
InputGroupCallId input_group_call_id;
bool is_ended;
std::tie(input_group_call_id, is_ended) = get_message_content_group_call_info(m->content.get());
d->has_expected_active_group_call_id = true;
if (is_ended) {
d->expected_active_group_call_id = InputGroupCallId();
if (d->active_group_call_id == input_group_call_id) {
on_update_dialog_group_call_id(d->dialog_id, InputGroupCallId());
}
} else {
d->expected_active_group_call_id = input_group_call_id;
if (d->active_group_call_id != input_group_call_id && !td_->auth_manager_->is_bot()) {
repair_dialog_active_group_call_id(d->dialog_id);
}
}
}
void MessagesManager::speculatively_update_channel_participants(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (!m->message_id.is_any_server() || dialog_id.get_type() != DialogType::Channel || !m->sender_user_id.is_valid()) {
return;
}
auto channel_id = dialog_id.get_channel_id();
UserId my_user_id(td_->contacts_manager_->get_my_id());
bool by_me = m->sender_user_id == my_user_id;
switch (m->content->get_type()) {
case MessageContentType::ChatAddUsers:
send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id,
get_message_content_added_user_ids(m->content.get()), m->sender_user_id, m->date, by_me);
break;
case MessageContentType::ChatJoinedByLink:
send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id,
vector<UserId>{m->sender_user_id}, m->sender_user_id, m->date, by_me);
break;
case MessageContentType::ChatDeleteUser:
send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_delete_channel_participant, channel_id,
get_message_content_deleted_user_id(m->content.get()), by_me);
break;
default:
break;
}
}
void MessagesManager::update_sent_message_contents(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
dialog_id.get_type() == DialogType::SecretChat || m->message_id.is_local() || m->forward_info != nullptr ||
m->had_forward_info) {
return;
}
on_sent_message_content(td_, m->content.get());
}
void MessagesManager::update_used_hashtags(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
m->via_bot_user_id.is_valid() || m->hide_via_bot || m->forward_info != nullptr || m->had_forward_info) {
return;
}
::td::update_used_hashtags(td_, m->content.get());
}
void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
auto dialog_type = dialog_id.get_type();
if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
dialog_type == DialogType::SecretChat || !m->message_id.is_any_server()) {
return;
}
bool is_forward = m->forward_info != nullptr || m->had_forward_info;
if (m->via_bot_user_id.is_valid() && !is_forward) {
// forwarded game messages can't be distinguished from sent via bot game messages, so increase rating anyway
on_dialog_used(TopDialogCategory::BotInline, DialogId(m->via_bot_user_id), m->date);
}
if (is_forward) {
auto &last_forward_date = last_outgoing_forwarded_message_date_[dialog_id];
if (last_forward_date < m->date) {
TopDialogCategory category =
dialog_type == DialogType::User ? TopDialogCategory::ForwardUsers : TopDialogCategory::ForwardChats;
on_dialog_used(category, dialog_id, m->date);
last_forward_date = m->date;
}
}
TopDialogCategory category = TopDialogCategory::Size;
switch (dialog_type) {
case DialogType::User: {
if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
category = TopDialogCategory::BotPM;
} else {
category = TopDialogCategory::Correspondent;
}
break;
}
case DialogType::Chat:
category = TopDialogCategory::Group;
break;
case DialogType::Channel:
switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) {
case ChannelType::Broadcast:
category = TopDialogCategory::Channel;
break;
case ChannelType::Megagroup:
category = TopDialogCategory::Group;
break;
case ChannelType::Unknown:
break;
default:
UNREACHABLE();
break;
}
break;
case DialogType::SecretChat:
case DialogType::None:
default:
UNREACHABLE();
}
if (category != TopDialogCategory::Size) {
on_dialog_used(category, dialog_id, m->date);
}
}
void MessagesManager::update_forward_count(DialogId dialog_id, const Message *m) {
if (!td_->auth_manager_->is_bot() && m->forward_info != nullptr && m->forward_info->sender_dialog_id.is_valid() &&
m->forward_info->message_id.is_valid() &&
(!is_discussion_message(dialog_id, m) || m->forward_info->sender_dialog_id != m->forward_info->from_dialog_id ||
m->forward_info->message_id != m->forward_info->from_message_id)) {
update_forward_count(m->forward_info->sender_dialog_id, m->forward_info->message_id, m->date);
}
}
void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date) {
CHECK(!td_->auth_manager_->is_bot());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
Message *m = get_message_force(d, message_id, "update_forward_count");
if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0 &&
m->interaction_info_update_date < update_date) {
if (m->forward_count == 0) {
m->forward_count++;
send_update_message_interaction_info(dialog_id, m);
on_message_changed(d, m, true, "update_forward_count");
}
if (d->pending_viewed_message_ids.insert(m->message_id).second) {
pending_message_views_timeout_.add_timeout_in(dialog_id.get(), 0.0);
}
}
}
void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id())) {
return;
}
Dialog *d = nullptr;
switch (dialog_id.get_type()) {
case DialogType::User:
d = get_dialog(dialog_id);
break;
case DialogType::Chat:
case DialogType::Channel:
break;
case DialogType::SecretChat: {
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
if (user_id.is_valid()) {
d = get_dialog_force(DialogId(user_id), "update_has_outgoing_messages");
}
break;
}
default:
UNREACHABLE();
}
if (d == nullptr || d->has_outgoing_messages) {
return;
}
d->has_outgoing_messages = true;
on_dialog_updated(dialog_id, "update_has_outgoing_messages");
if (d->action_bar != nullptr && d->action_bar->on_outgoing_message()) {
send_update_chat_action_bar(d);
}
}
void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) {
if (m->reply_to_message_id == MessageId() || !m->reply_to_message_id.is_yet_unsent()) {
return;
}
auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id");
if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
LOG(INFO) << "Failed to find replied " << m->reply_to_message_id << " with random_id = " << m->reply_to_random_id;
m->reply_to_message_id = m->top_thread_message_id;
m->reply_to_random_id = 0;
} else {
LOG(INFO) << "Restore message reply to " << message_id << " with random_id = " << m->reply_to_random_id;
m->reply_to_message_id = message_id;
}
}
MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m,
uint64 log_event_id) {
CHECK(log_event_id != 0);
CHECK(m != nullptr);
CHECK(m->content != nullptr);
Dialog *d = get_dialog_force(dialog_id, "continue_send_message");
if (d == nullptr) {
LOG(ERROR) << "Can't find " << dialog_id << " to continue send a message";
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
return nullptr;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), log_event_id);
return nullptr;
}
LOG(INFO) << "Continue to send " << m->message_id << " to " << dialog_id << " initially sent at " << m->send_date
<< " from binlog";
d->was_opened = true;
auto now = G()->unix_time();
if (m->message_id.is_scheduled()) {
set_message_id(m, get_next_yet_unsent_scheduled_message_id(d, m->date));
} else {
set_message_id(m, get_next_yet_unsent_message_id(d));
m->date = now;
}
m->have_previous = true;
m->have_next = true;
restore_message_reply_to_message_id(d, m.get());
bool need_update = false;
bool need_update_dialog_pos = false;
auto result_message =
add_message_to_dialog(d, std::move(m), true, &need_update, &need_update_dialog_pos, "continue_send_message");
CHECK(result_message != nullptr);
if (result_message->message_id.is_scheduled()) {
send_update_chat_has_scheduled_messages(d, false);
}
send_update_new_message(d, result_message);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "continue_send_message");
}
auto can_send_status = can_send_message(dialog_id);
if (can_send_status.is_ok() && result_message->send_date < now - MAX_RESEND_DELAY &&
dialog_id != get_my_dialog_id()) {
can_send_status = Status::Error(400, "Message is too old to be re-sent automatically");
}
if (can_send_status.is_error()) {
LOG(INFO) << "Can't continue to send a message to " << dialog_id << ": " << can_send_status;
fail_send_message({dialog_id, result_message->message_id}, std::move(can_send_status));
return nullptr;
}
return result_message;
}
void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
if (G()->close_flag()) {
return;
}
for (auto &event : events) {
CHECK(event.id_ != 0);
switch (event.type_) {
case LogEvent::HandlerType::SendMessage: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SendMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
m->send_message_log_event_id = event.id_;
if (m->content->get_type() == MessageContentType::Unsupported) {
LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(Slice(event.data_));
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
add_message_dependencies(dependencies, m.get());
dependencies.resolve_force(td_, "SendMessageLogEvent");
m->content =
dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send, MessageCopyOptions());
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
do_send_message(dialog_id, result_message);
}
break;
}
case LogEvent::HandlerType::SendBotStartMessage: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SendBotStartMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
m->send_message_log_event_id = event.id_;
CHECK(m->content->get_type() == MessageContentType::Text);
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
add_message_dependencies(dependencies, m.get());
dependencies.resolve_force(td_, "SendBotStartMessageLogEvent");
auto bot_user_id = log_event.bot_user_id;
if (!td_->contacts_manager_->have_user_force(bot_user_id)) {
LOG(ERROR) << "Can't find bot " << bot_user_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id,
result_message->message_id, log_event.parameter);
}
break;
}
case LogEvent::HandlerType::SendInlineQueryResultMessage: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SendInlineQueryResultMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
m->send_message_log_event_id = event.id_;
if (m->content->get_type() == MessageContentType::Unsupported) {
LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(Slice(event.data_));
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
add_message_dependencies(dependencies, m.get());
dependencies.resolve_force(td_, "SendInlineQueryResultMessageLogEvent");
m->content = dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::SendViaBot,
MessageCopyOptions());
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id,
result_message->message_id, log_event.query_id, log_event.result_id);
}
break;
}
case LogEvent::HandlerType::SendScreenshotTakenNotificationMessage: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SendScreenshotTakenNotificationMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
m->send_message_log_event_id = 0; // to not allow event deletion by message deletion
CHECK(m->content->get_type() == MessageContentType::ScreenshotTaken);
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
add_message_dependencies(dependencies, m.get());
dependencies.resolve_force(td_, "SendScreenshotTakenNotificationMessageLogEvent");
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
do_send_screenshot_taken_notification_message(dialog_id, result_message, event.id_);
}
break;
}
case LogEvent::HandlerType::ForwardMessages: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
ForwardMessagesLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto to_dialog_id = log_event.to_dialog_id;
auto from_dialog_id = log_event.from_dialog_id;
auto messages = std::move(log_event.messages_out);
Dependencies dependencies;
dependencies.add_dialog_dependencies(to_dialog_id);
dependencies.add_dialog_dependencies(from_dialog_id);
for (auto &m : messages) {
add_message_dependencies(dependencies, m.get());
}
dependencies.resolve_force(td_, "ForwardMessagesLogEvent");
Dialog *to_dialog = get_dialog_force(to_dialog_id, "ForwardMessagesLogEvent to");
if (to_dialog == nullptr) {
LOG(ERROR) << "Can't find " << to_dialog_id << " to forward messages to";
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
Dialog *from_dialog = get_dialog_force(from_dialog_id, "ForwardMessagesLogEvent from");
if (from_dialog == nullptr) {
LOG(ERROR) << "Can't find " << from_dialog_id << " to forward messages from";
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
to_dialog->was_opened = true;
auto now = G()->unix_time();
if (!have_input_peer(from_dialog_id, AccessRights::Read) || can_send_message(to_dialog_id).is_error() ||
messages.empty() ||
(messages[0]->send_date < now - MAX_RESEND_DELAY && to_dialog_id != get_my_dialog_id())) {
LOG(WARNING) << "Can't continue forwarding " << messages.size() << " message(s) to " << to_dialog_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
LOG(INFO) << "Continue to forward " << messages.size() << " message(s) to " << to_dialog_id << " from binlog";
bool need_update = false;
bool need_update_dialog_pos = false;
vector<Message *> forwarded_messages;
for (auto &m : messages) {
if (m->message_id.is_scheduled()) {
set_message_id(m, get_next_yet_unsent_scheduled_message_id(to_dialog, m->date));
} else {
set_message_id(m, get_next_yet_unsent_message_id(to_dialog));
m->date = now;
}
m->content = dup_message_content(td_, to_dialog_id, m->content.get(), MessageContentDupType::Forward,
MessageCopyOptions());
CHECK(m->content != nullptr);
m->have_previous = true;
m->have_next = true;
restore_message_reply_to_message_id(to_dialog, m.get());
forwarded_messages.push_back(add_message_to_dialog(to_dialog, std::move(m), true, &need_update,
&need_update_dialog_pos, "forward message again"));
send_update_new_message(to_dialog, forwarded_messages.back());
}
send_update_chat_has_scheduled_messages(to_dialog, false);
if (need_update_dialog_pos) {
send_update_chat_last_message(to_dialog, "on_reforward_message");
}
do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, log_event.message_ids,
log_event.drop_author, log_event.drop_media_captions, event.id_);
break;
}
case LogEvent::HandlerType::DeleteMessage: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
log_event.id_ = event.id_;
Dialog *d = get_dialog_force(log_event.full_message_id_.get_dialog_id(), "DeleteMessageLogEvent");
if (d != nullptr) {
auto message_id = log_event.full_message_id_.get_message_id();
if (message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
} else if (message_id != MessageId()) {
d->deleted_message_ids.insert(message_id);
}
}
do_delete_message_log_event(log_event);
break;
}
case LogEvent::HandlerType::DeleteMessagesOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteMessagesOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "DeleteMessagesOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
for (auto message_id : log_event.message_ids_) {
CHECK(message_id.is_valid());
d->deleted_message_ids.insert(message_id);
}
delete_messages_on_server(dialog_id, std::move(log_event.message_ids_), log_event.revoke_, event.id_, Auto());
break;
}
case LogEvent::HandlerType::DeleteScheduledMessagesOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteScheduledMessagesOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "DeleteScheduledMessagesOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
for (auto message_id : log_event.message_ids_) {
CHECK(message_id.is_scheduled_server());
d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
}
delete_scheduled_messages_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto());
break;
}
case LogEvent::HandlerType::DeleteDialogHistoryOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteDialogHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "DeleteDialogHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
delete_dialog_history_on_server(dialog_id, log_event.max_message_id_, log_event.remove_from_dialog_list_,
log_event.revoke_, true, event.id_, Auto());
break;
}
case LogEvent::HandlerType::DeleteTopicHistoryOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteTopicHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "DeleteTopicHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
delete_topic_history_on_server(dialog_id, log_event.top_thread_message_id_, event.id_, Auto());
break;
}
case LogEvent::HandlerType::DeleteAllCallMessagesOnServer: {
DeleteAllCallMessagesOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
delete_all_call_messages_on_server(log_event.revoke_, event.id_, Auto());
break;
}
case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer: {
BlockMessageSenderFromRepliesOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
block_message_sender_from_replies_on_server(log_event.message_id_, log_event.delete_message_,
log_event.delete_all_messages_, log_event.report_spam_, event.id_,
Auto());
break;
}
case LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer: {
if (!G()->parameters().use_chat_info_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto channel_id = log_event.channel_id_;
if (!td_->contacts_manager_->have_channel_force(channel_id)) {
LOG(ERROR) << "Can't find " << channel_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
auto sender_dialog_id = log_event.sender_dialog_id_;
if (!have_dialog_info_force(sender_dialog_id) || !have_input_peer(sender_dialog_id, AccessRights::Know)) {
LOG(ERROR) << "Can't find " << sender_dialog_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, event.id_, Auto());
break;
}
case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteDialogMessagesByDateOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "DeleteDialogMessagesByDateOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
delete_dialog_messages_by_date_on_server(dialog_id, log_event.min_date_, log_event.max_date_, log_event.revoke_,
event.id_, Auto());
break;
}
case LogEvent::HandlerType::ReadHistoryOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReadHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ReadHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
if (d->read_history_log_event_ids[0].log_event_id != 0) {
// we need only latest read history event
binlog_erase(G()->td_db()->get_binlog(), d->read_history_log_event_ids[0].log_event_id);
}
d->read_history_log_event_ids[0].log_event_id = event.id_;
read_history_on_server_impl(d, log_event.max_message_id_);
break;
}
case LogEvent::HandlerType::ReadHistoryInSecretChat: {
ReadHistoryInSecretChatLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
CHECK(dialog_id.get_type() == DialogType::SecretChat);
if (!td_->contacts_manager_->have_secret_chat_force(dialog_id.get_secret_chat_id())) {
LOG(ERROR) << "Can't read history in unknown " << dialog_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
force_create_dialog(dialog_id, "ReadHistoryInSecretChatLogEvent");
Dialog *d = get_dialog(dialog_id);
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
if (d->read_history_log_event_ids[0].log_event_id != 0) {
// we need only latest read history event
binlog_erase(G()->td_db()->get_binlog(), d->read_history_log_event_ids[0].log_event_id);
}
d->read_history_log_event_ids[0].log_event_id = event.id_;
d->last_read_inbox_message_date = log_event.max_date_;
read_history_on_server_impl(d, MessageId());
break;
}
case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReadMessageThreadHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ReadMessageThreadHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
auto top_thread_message_id = log_event.top_thread_message_id_;
if (d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id != 0) {
// we need only latest read history event
binlog_erase(G()->td_db()->get_binlog(),
d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id);
}
d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id = event.id_;
read_message_thread_history_on_server_impl(d, top_thread_message_id, log_event.max_message_id_);
break;
}
case LogEvent::HandlerType::ReadMessageContentsOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReadMessageContentsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ReadMessageContentsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
read_message_contents_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto());
break;
}
case LogEvent::HandlerType::ReadAllDialogMentionsOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReadAllDialogMentionsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogMentionsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
read_all_dialog_mentions_on_server(dialog_id, event.id_, Promise<Unit>());
break;
}
case LogEvent::HandlerType::ReadAllDialogReactionsOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReadAllDialogReactionsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogReactionsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
read_all_dialog_reactions_on_server(dialog_id, event.id_, Promise<Unit>());
break;
}
case LogEvent::HandlerType::ToggleDialogIsPinnedOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ToggleDialogIsPinnedOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ToggleDialogIsPinnedOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
toggle_dialog_is_pinned_on_server(dialog_id, log_event.is_pinned_, event.id_);
break;
}
case LogEvent::HandlerType::ReorderPinnedDialogsOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ReorderPinnedDialogsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
vector<DialogId> dialog_ids;
for (auto &dialog_id : log_event.dialog_ids_) {
Dialog *d = get_dialog_force(dialog_id, "ReorderPinnedDialogsOnServerLogEvent");
if (d != nullptr && have_input_peer(dialog_id, AccessRights::Read)) {
dialog_ids.push_back(dialog_id);
}
}
if (dialog_ids.empty()) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
reorder_pinned_dialogs_on_server(log_event.folder_id_, dialog_ids, event.id_);
break;
}
case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
bool have_info = dialog_id.get_type() == DialogType::User
? td_->contacts_manager_->have_user_force(dialog_id.get_user_id())
: have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent");
if (!have_info || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
toggle_dialog_is_marked_as_unread_on_server(dialog_id, log_event.is_marked_as_unread_, event.id_);
break;
}
case LogEvent::HandlerType::ToggleDialogIsBlockedOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ToggleDialogIsBlockedOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
if (dialog_id.get_type() == DialogType::SecretChat || !have_dialog_info_force(dialog_id) ||
!have_input_peer(dialog_id, AccessRights::Know)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
toggle_dialog_is_blocked_on_server(dialog_id, log_event.is_blocked_, event.id_);
break;
}
case LogEvent::HandlerType::SaveDialogDraftMessageOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SaveDialogDraftMessageOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "SaveDialogDraftMessageOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Write)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
d->save_draft_message_log_event_id.log_event_id = event.id_;
save_dialog_draft_message_on_server(dialog_id);
break;
}
case LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
UpdateDialogNotificationSettingsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "UpdateDialogNotificationSettingsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
d->save_notification_settings_log_event_id.log_event_id = event.id_;
update_dialog_notification_settings_on_server(dialog_id, true);
break;
}
case LogEvent::HandlerType::ResetAllNotificationSettingsOnServer: {
ResetAllNotificationSettingsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
reset_all_notification_settings_on_server(event.id_);
break;
}
case LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
ToggleDialogReportSpamStateOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "ToggleDialogReportSpamStateOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
toggle_dialog_report_spam_state_on_server(dialog_id, log_event.is_spam_dialog_, event.id_, Promise<Unit>());
break;
}
case LogEvent::HandlerType::SetDialogFolderIdOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
SetDialogFolderIdOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dialog *d = get_dialog_force(dialog_id, "SetDialogFolderIdOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
d->set_folder_id_log_event_id.log_event_id = event.id_;
set_dialog_folder_id(d, log_event.folder_id_);
set_dialog_folder_id_on_server(dialog_id, true);
break;
}
case LogEvent::HandlerType::RegetDialog: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
RegetDialogLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
Dependencies dependencies;
dependencies.add_dialog_dependencies(dialog_id);
dependencies.resolve_force(td_, "RegetDialogLogEvent");
get_dialog_force(dialog_id, "RegetDialogLogEvent"); // load it if exists
if (!have_input_peer(dialog_id, AccessRights::Read)) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
send_get_dialog_query(dialog_id, Auto(), event.id_, "RegetDialogLogEvent");
break;
}
case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer: {
if (!G()->parameters().use_message_db) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
UnpinAllDialogMessagesOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
unpin_all_dialog_messages_on_server(log_event.dialog_id_, event.id_, Auto());
break;
}
case LogEvent::HandlerType::GetChannelDifference: {
if (G()->ignore_background_updates()) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
GetChannelDifferenceLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
DialogId dialog_id(log_event.channel_id);
if (dialog_id.get_type() != DialogType::Channel) {
LOG(ERROR) << "Trying to run GetChannelDifference in " << dialog_id;
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
LOG(INFO) << "Continue to run getChannelDifference in " << dialog_id;
get_channel_difference_to_log_event_id_.emplace(dialog_id, event.id_);
do_get_channel_difference(
dialog_id, load_channel_pts(dialog_id), true,
telegram_api::make_object<telegram_api::inputChannel>(log_event.channel_id.get(), log_event.access_hash),
false, "LogEvent::HandlerType::GetChannelDifference");
break;
}
default:
LOG(FATAL) << "Unsupported log event type " << event.type_;
}
}
}
Status MessagesManager::add_recently_found_dialog(DialogId dialog_id) {
if (!have_dialog_force(dialog_id, "add_recently_found_dialog")) {
return Status::Error(400, "Chat not found");
}
recently_found_dialogs_.add_dialog(dialog_id);
return Status::OK();
}
Status MessagesManager::remove_recently_found_dialog(DialogId dialog_id) {
if (!have_dialog_force(dialog_id, "remove_recently_found_dialog")) {
return Status::Error(400, "Chat not found");
}
recently_found_dialogs_.remove_dialog(dialog_id);
return Status::OK();
}
void MessagesManager::clear_recently_found_dialogs() {
recently_found_dialogs_.clear_dialogs();
}
void MessagesManager::suffix_load_loop(Dialog *d) {
if (d->suffix_load_has_query_) {
return;
}
if (d->suffix_load_queries_.empty()) {
return;
}
CHECK(!d->suffix_load_done_);
auto dialog_id = d->dialog_id;
auto from_message_id = d->suffix_load_first_message_id_;
LOG(INFO) << "Send suffix load query in " << dialog_id << " from " << from_message_id;
auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::suffix_load_query_ready, dialog_id);
});
d->suffix_load_has_query_ = true;
d->suffix_load_query_message_id_ = from_message_id;
if (from_message_id.is_valid()) {
get_history_impl(d, from_message_id, -1, 100, true, true, std::move(promise));
} else {
CHECK(from_message_id == MessageId());
get_history_from_the_end_impl(d, true, true, std::move(promise), "suffix_load_loop");
}
}
void MessagesManager::suffix_load_update_first_message_id(Dialog *d) {
if (!d->suffix_load_first_message_id_.is_valid()) {
if (!d->last_message_id.is_valid()) {
return;
}
d->suffix_load_first_message_id_ = d->last_message_id;
}
auto it = MessagesConstIterator(d, d->suffix_load_first_message_id_);
CHECK(*it != nullptr);
CHECK((*it)->message_id == d->suffix_load_first_message_id_);
while ((*it)->have_previous) {
--it;
}
d->suffix_load_first_message_id_ = (*it)->message_id;
}
void MessagesManager::suffix_load_query_ready(DialogId dialog_id) {
LOG(INFO) << "Finished suffix load query in " << dialog_id;
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
bool is_unchanged = d->suffix_load_first_message_id_ == d->suffix_load_query_message_id_;
suffix_load_update_first_message_id(d);
if (is_unchanged && d->suffix_load_first_message_id_ == d->suffix_load_query_message_id_) {
LOG(INFO) << "Finished suffix load in " << dialog_id;
d->suffix_load_done_ = true;
}
d->suffix_load_has_query_ = false;
// Remove ready queries
auto *m = get_message_force(d, d->suffix_load_first_message_id_, "suffix_load_query_ready");
auto ready_it = std::partition(d->suffix_load_queries_.begin(), d->suffix_load_queries_.end(),
[&](auto &value) { return !(d->suffix_load_done_ || value.second(m)); });
for (auto it = ready_it; it != d->suffix_load_queries_.end(); ++it) {
it->first.set_value(Unit());
}
d->suffix_load_queries_.erase(ready_it, d->suffix_load_queries_.end());
suffix_load_loop(d);
}
void MessagesManager::suffix_load_add_query(Dialog *d,
std::pair<Promise<Unit>, std::function<bool(const Message *)>> query) {
suffix_load_update_first_message_id(d);
auto *m = get_message_force(d, d->suffix_load_first_message_id_, "suffix_load_add_query");
if (d->suffix_load_done_ || query.second(m)) {
query.first.set_value(Unit());
} else {
d->suffix_load_queries_.emplace_back(std::move(query));
suffix_load_loop(d);
}
}
void MessagesManager::suffix_load_till_date(Dialog *d, int32 date, Promise<Unit> promise) {
LOG(INFO) << "Load suffix of " << d->dialog_id << " till date " << date;
auto condition = [date](const Message *m) {
return m != nullptr && m->date < date;
};
suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition)));
}
void MessagesManager::suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<Unit> promise) {
LOG(INFO) << "Load suffix of " << d->dialog_id << " till " << message_id;
auto condition = [message_id](const Message *m) {
return m != nullptr && m->message_id < message_id;
};
suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition)));
}
void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector<int32> &&option_ids,
Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "set_poll_answer");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (m->content->get_type() != MessageContentType::Poll) {
return promise.set_error(Status::Error(400, "Message is not a poll"));
}
if (m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Can't answer polls from scheduled messages"));
}
if (!m->message_id.is_server()) {
return promise.set_error(Status::Error(400, "Poll can't be answered"));
}
set_message_content_poll_answer(td_, m->content.get(), full_message_id, std::move(option_ids), std::move(promise));
}
void MessagesManager::get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
auto m = get_message_force(full_message_id, "get_poll_voters");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (m->content->get_type() != MessageContentType::Poll) {
return promise.set_error(Status::Error(400, "Message is not a poll"));
}
if (m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Can't get poll results from scheduled messages"));
}
if (!m->message_id.is_server()) {
return promise.set_error(Status::Error(400, "Poll results can't be received"));
}
get_message_content_poll_voters(td_, m->content.get(), full_message_id, option_id, offset, limit, std::move(promise));
}
void MessagesManager::stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "stop_poll");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
return promise.set_error(Status::Error(400, "Can't access the chat"));
}
if (m->content->get_type() != MessageContentType::Poll) {
return promise.set_error(Status::Error(400, "Message is not a poll"));
}
if (get_message_content_poll_is_closed(td_, m->content.get())) {
return promise.set_error(Status::Error(400, "Poll has already been closed"));
}
if (!can_edit_message(full_message_id.get_dialog_id(), m, true)) {
return promise.set_error(Status::Error(400, "Poll can't be stopped"));
}
if (m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(400, "Can't stop polls from scheduled messages"));
}
if (!m->message_id.is_server()) {
return promise.set_error(Status::Error(400, "Poll can't be stopped"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
has_message_sender_user_id(full_message_id.get_dialog_id(), m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
stop_message_content_poll(td_, m->content.get(), full_message_id, r_new_reply_markup.move_as_ok(),
std::move(promise));
}
Result<ServerMessageId> MessagesManager::get_invoice_message_id(FullMessageId full_message_id) {
auto m = get_message_force(full_message_id, "get_invoice_message_id");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (m->content->get_type() != MessageContentType::Invoice) {
return Status::Error(400, "Message has no invoice");
}
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Wrong scheduled message identifier");
}
if (!m->message_id.is_server()) {
return Status::Error(400, "Wrong message identifier");
}
if (m->reply_markup == nullptr || m->reply_markup->inline_keyboard.empty() ||
m->reply_markup->inline_keyboard[0].empty() ||
m->reply_markup->inline_keyboard[0][0].type != InlineKeyboardButton::Type::Buy) {
return Status::Error(400, "Message has no Pay button");
}
return m->message_id.get_server_message_id();
}
Result<ServerMessageId> MessagesManager::get_payment_successful_message_id(FullMessageId full_message_id) {
auto m = get_message_force(full_message_id, "get_payment_successful_message_id");
if (m == nullptr) {
return Status::Error(400, "Message not found");
}
if (m->content->get_type() != MessageContentType::PaymentSuccessful) {
return Status::Error(400, "Message has wrong type");
}
if (m->message_id.is_scheduled()) {
return Status::Error(400, "Wrong scheduled message identifier");
}
if (!m->message_id.is_server()) {
return Status::Error(400, "Wrong message identifier");
}
return m->message_id.get_server_message_id();
}
void MessagesManager::remove_sponsored_dialog() {
set_sponsored_dialog(DialogId(), DialogSource());
}
void MessagesManager::on_get_sponsored_dialog(tl_object_ptr<telegram_api::Peer> peer, DialogSource source,
vector<tl_object_ptr<telegram_api::User>> users,
vector<tl_object_ptr<telegram_api::Chat>> chats) {
CHECK(peer != nullptr);
td_->contacts_manager_->on_get_users(std::move(users), "on_get_sponsored_dialog");
td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_sponsored_dialog");
set_sponsored_dialog(DialogId(peer), std::move(source));
}
void MessagesManager::add_sponsored_dialog(const Dialog *d, DialogSource source) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(!sponsored_dialog_id_.is_valid());
sponsored_dialog_id_ = d->dialog_id;
sponsored_dialog_source_ = std::move(source);
// update last_pinned_dialog_date in any case, because all chats before SPONSORED_DIALOG_ORDER are known
auto dialog_list_id = DialogListId(FolderId::main());
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
DialogDate max_dialog_date(SPONSORED_DIALOG_ORDER, d->dialog_id);
if (list->last_pinned_dialog_date_ < max_dialog_date) {
list->last_pinned_dialog_date_ = max_dialog_date;
update_list_last_dialog_date(*list);
}
if (is_dialog_sponsored(d)) {
send_update_chat_position(dialog_list_id, d, "add_sponsored_dialog");
// the sponsored dialog must not be saved there
}
}
void MessagesManager::save_sponsored_dialog() {
if (!G()->parameters().use_message_db) {
return;
}
LOG(INFO) << "Save sponsored " << sponsored_dialog_id_ << " with source " << sponsored_dialog_source_;
if (sponsored_dialog_id_.is_valid()) {
G()->td_db()->get_binlog_pmc()->set(
"sponsored_dialog_id",
PSTRING() << sponsored_dialog_id_.get() << ' ' << sponsored_dialog_source_.DialogSource::serialize());
} else {
G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id");
}
}
void MessagesManager::set_sponsored_dialog(DialogId dialog_id, DialogSource source) {
if (td_->auth_manager_->is_bot()) {
return;
}
LOG(INFO) << "Change sponsored chat from " << sponsored_dialog_id_ << " to " << dialog_id;
if (removed_sponsored_dialog_id_.is_valid() && dialog_id == removed_sponsored_dialog_id_) {
return;
}
if (sponsored_dialog_id_ == dialog_id) {
if (sponsored_dialog_source_ != source) {
CHECK(sponsored_dialog_id_.is_valid());
sponsored_dialog_source_ = std::move(source);
const Dialog *d = get_dialog(sponsored_dialog_id_);
CHECK(d != nullptr);
send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog");
save_sponsored_dialog();
}
return;
}
bool need_update_total_chat_count = false;
if (sponsored_dialog_id_.is_valid()) {
const Dialog *d = get_dialog(sponsored_dialog_id_);
CHECK(d != nullptr);
bool was_sponsored = is_dialog_sponsored(d);
sponsored_dialog_id_ = DialogId();
sponsored_dialog_source_ = DialogSource();
if (was_sponsored) {
send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog 2");
need_update_total_chat_count = true;
}
}
if (dialog_id.is_valid()) {
force_create_dialog(dialog_id, "set_sponsored_dialog_id");
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
add_sponsored_dialog(d, std::move(source));
if (is_dialog_sponsored(d)) {
need_update_total_chat_count = !need_update_total_chat_count;
}
}
if (need_update_total_chat_count) {
auto dialog_list_id = DialogListId(FolderId::main());
auto *list = get_dialog_list(dialog_list_id);
CHECK(list != nullptr);
if (list->is_dialog_unread_count_inited_) {
send_update_unread_chat_count(*list, DialogId(), true, "set_sponsored_dialog_id");
}
}
save_sponsored_dialog();
}
td_api::object_ptr<td_api::updateChatFilters> MessagesManager::get_update_chat_filters_object() const {
CHECK(!td_->auth_manager_->is_bot());
auto update = td_api::make_object<td_api::updateChatFilters>();
for (const auto &filter : dialog_filters_) {
update->chat_filters_.push_back(filter->get_chat_filter_info_object());
}
update->main_chat_list_position_ = main_dialog_list_position_;
return update;
}
td_api::object_ptr<td_api::updateUnreadMessageCount> MessagesManager::get_update_unread_message_count_object(
const DialogList &list) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(list.is_message_unread_count_inited_);
int32 unread_count = list.unread_message_total_count_;
int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_;
CHECK(unread_count >= 0);
CHECK(unread_unmuted_count >= 0);
return td_api::make_object<td_api::updateUnreadMessageCount>(list.dialog_list_id.get_chat_list_object(), unread_count,
unread_unmuted_count);
}
td_api::object_ptr<td_api::updateUnreadChatCount> MessagesManager::get_update_unread_chat_count_object(
const DialogList &list) const {
CHECK(!td_->auth_manager_->is_bot());
CHECK(list.is_dialog_unread_count_inited_);
int32 unread_count = list.unread_dialog_total_count_;
int32 unread_unmuted_count = unread_count - list.unread_dialog_muted_count_;
int32 unread_marked_count = list.unread_dialog_marked_count_;
int32 unread_unmuted_marked_count = unread_marked_count - list.unread_dialog_muted_marked_count_;
CHECK(unread_count >= 0);
CHECK(unread_unmuted_count >= 0);
CHECK(unread_marked_count >= 0);
CHECK(unread_unmuted_marked_count >= 0);
return td_api::make_object<td_api::updateUnreadChatCount>(
list.dialog_list_id.get_chat_list_object(), get_dialog_total_count(list), unread_count, unread_unmuted_count,
unread_marked_count, unread_unmuted_marked_count);
}
void MessagesManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
if (!td_->auth_manager_->is_bot()) {
if (!dialog_filters_.empty()) {
updates.push_back(get_update_chat_filters_object());
}
if (G()->parameters().use_message_db) {
for (const auto &it : dialog_lists_) {
auto &list = it.second;
if (list.is_message_unread_count_inited_) {
updates.push_back(get_update_unread_message_count_object(list));
}
if (list.is_dialog_unread_count_inited_) {
updates.push_back(get_update_unread_chat_count_object(list));
}
}
}
}
vector<td_api::object_ptr<td_api::Update>> last_message_updates;
dialogs_.foreach([&](const DialogId &dialog_id, const unique_ptr<Dialog> &dialog) {
const Dialog *d = dialog.get();
auto update = td_api::make_object<td_api::updateNewChat>(get_chat_object(d));
if (update->chat_->last_message_ != nullptr && update->chat_->last_message_->forward_info_ != nullptr) {
last_message_updates.push_back(td_api::make_object<td_api::updateChatLastMessage>(
dialog_id.get(), std::move(update->chat_->last_message_), get_chat_positions_object(d)));
}
updates.push_back(std::move(update));
if (d->is_opened) {
auto info_it = dialog_online_member_counts_.find(dialog_id);
if (info_it != dialog_online_member_counts_.end() && info_it->second.is_update_sent) {
updates.push_back(td_api::make_object<td_api::updateChatOnlineMemberCount>(
dialog_id.get(), info_it->second.online_member_count));
}
}
});
append(updates, std::move(last_message_updates));
}
void MessagesManager::add_message_file_to_downloads(FullMessageId full_message_id, FileId file_id, int32 priority,
Promise<td_api::object_ptr<td_api::file>> promise) {
auto m = get_message_force(full_message_id, "add_message_file_to_downloads");
if (m == nullptr) {
return promise.set_error(Status::Error(400, "Message not found"));
}
auto file_view = td_->file_manager_->get_file_view(file_id);
if (file_view.empty()) {
return promise.set_error(Status::Error(400, "File not found"));
}
file_id = file_view.get_main_file_id();
bool is_found = false;
for (auto message_file_id : get_message_file_ids(m)) {
auto message_file_view = td_->file_manager_->get_file_view(message_file_id);
CHECK(!message_file_view.empty());
if (message_file_view.get_main_file_id() == file_id) {
is_found = true;
}
}
if (!is_found) {
return promise.set_error(Status::Error(400, "Message has no specified file"));
}
if (m->message_id.is_yet_unsent()) {
return promise.set_error(Status::Error(400, "Yet unsent messages can't be added to Downloads"));
}
auto search_text = get_message_search_text(m);
auto file_source_id = get_message_file_source_id(full_message_id, true);
CHECK(file_source_id.is_valid());
send_closure(td_->download_manager_actor_, &DownloadManager::add_file, file_id, file_source_id,
std::move(search_text), static_cast<int8>(priority), std::move(promise));
}
void MessagesManager::get_message_file_search_text(FullMessageId full_message_id, string unique_file_id,
Promise<string> promise) {
auto m = get_message_force(full_message_id, "get_message_file_search_text");
if (m == nullptr) {
return promise.set_error(Status::Error(200, "Message not found"));
}
for (auto file_id : get_message_file_ids(m)) {
auto file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.empty());
if (file_view.get_unique_file_id() == unique_file_id) {
return promise.set_value(get_message_search_text(m));
}
}
return promise.set_error(Status::Error(200, "File not found"));
}
} // namespace td