2024-02-21 18:43:55 +01:00
|
|
|
//
|
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
|
|
|
//
|
|
|
|
// 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/QuickReplyManager.h"
|
|
|
|
|
2024-02-21 20:06:07 +01:00
|
|
|
#include "td/telegram/AuthManager.h"
|
2024-02-21 20:43:29 +01:00
|
|
|
#include "td/telegram/ContactsManager.h"
|
2024-02-21 20:06:07 +01:00
|
|
|
#include "td/telegram/Dependencies.h"
|
|
|
|
#include "td/telegram/DialogManager.h"
|
2024-02-22 15:32:12 +01:00
|
|
|
#include "td/telegram/Global.h"
|
2024-02-21 20:06:07 +01:00
|
|
|
#include "td/telegram/MessageContent.h"
|
|
|
|
#include "td/telegram/MessageForwardInfo.h"
|
|
|
|
#include "td/telegram/MessageReplyHeader.h"
|
2024-02-21 22:48:05 +01:00
|
|
|
#include "td/telegram/MessageSelfDestructType.h"
|
2024-02-21 20:27:46 +01:00
|
|
|
#include "td/telegram/RepliedMessageInfo.h"
|
2024-02-21 20:06:07 +01:00
|
|
|
#include "td/telegram/Td.h"
|
|
|
|
|
2024-02-22 15:32:12 +01:00
|
|
|
#include "td/utils/algorithm.h"
|
|
|
|
#include "td/utils/buffer.h"
|
|
|
|
#include "td/utils/FlatHashSet.h"
|
|
|
|
#include "td/utils/logging.h"
|
|
|
|
|
2024-02-21 18:43:55 +01:00
|
|
|
namespace td {
|
|
|
|
|
2024-02-22 15:32:12 +01:00
|
|
|
class GetQuickRepliesQuery final : public Td::ResultHandler {
|
|
|
|
Promise<telegram_api::object_ptr<telegram_api::messages_QuickReplies>> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetQuickRepliesQuery(Promise<telegram_api::object_ptr<telegram_api::messages_QuickReplies>> &&promise)
|
|
|
|
: promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void send(int64 hash) {
|
|
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_getQuickReplies(hash)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
|
|
auto result_ptr = fetch_result<telegram_api::messages_getQuickReplies>(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 GetQuickRepliesQuery: " << to_string(ptr);
|
|
|
|
promise_.set_value(std::move(ptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
void on_error(Status status) final {
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
QuickReplyManager::QuickReplyMessage::~QuickReplyMessage() = default;
|
|
|
|
|
|
|
|
QuickReplyManager::Shortcut::~Shortcut() = default;
|
|
|
|
|
2024-02-21 18:43:55 +01:00
|
|
|
QuickReplyManager::QuickReplyManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::tear_down() {
|
|
|
|
parent_.reset();
|
|
|
|
}
|
|
|
|
|
2024-02-21 20:06:07 +01:00
|
|
|
unique_ptr<QuickReplyManager::QuickReplyMessage> QuickReplyManager::create_message(
|
|
|
|
telegram_api::object_ptr<telegram_api::Message> message_ptr, const char *source) const {
|
|
|
|
LOG(DEBUG) << "Receive from " << source << " " << to_string(message_ptr);
|
|
|
|
CHECK(message_ptr != nullptr);
|
|
|
|
|
|
|
|
switch (message_ptr->get_id()) {
|
|
|
|
case telegram_api::messageEmpty::ID:
|
|
|
|
break;
|
|
|
|
case telegram_api::message::ID: {
|
2024-02-22 15:32:12 +01:00
|
|
|
auto message_id = MessageId::get_message_id(message_ptr, false);
|
2024-02-21 20:06:07 +01:00
|
|
|
auto message = move_tl_object_as<telegram_api::message>(message_ptr);
|
|
|
|
if (message->quick_reply_shortcut_id_ == 0) {
|
|
|
|
LOG(ERROR) << "Receive a quick reply without shortcut from " << source;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto my_dialog_id = td_->dialog_manager_->get_my_dialog_id();
|
2024-02-22 15:32:12 +01:00
|
|
|
if (DialogId(message->peer_id_) != my_dialog_id || message->from_id_ != nullptr ||
|
|
|
|
message->saved_peer_id_ != nullptr || message->views_ != 0 || message->forwards_ != 0 ||
|
2024-02-21 20:06:07 +01:00
|
|
|
message->replies_ != nullptr || message->reactions_ != nullptr || message->edit_date_ != 0 ||
|
|
|
|
message->ttl_period_ != 0 || !message->out_ || message->post_ || message->edit_hide_ ||
|
|
|
|
message->from_scheduled_ || message->pinned_ || message->noforwards_ || message->mentioned_ ||
|
|
|
|
message->media_unread_ || message->reply_markup_ != nullptr || !message->restriction_reason_.empty() ||
|
2024-02-22 15:32:12 +01:00
|
|
|
!message->post_author_.empty() || message->from_boosts_applied_ != 0) {
|
2024-02-21 20:06:07 +01:00
|
|
|
LOG(ERROR) << "Receive an invalid quick reply from " << source << ": " << to_string(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto forward_header = std::move(message->fwd_from_);
|
|
|
|
UserId via_bot_user_id;
|
|
|
|
if (message->flags_ & telegram_api::message::VIA_BOT_ID_MASK) {
|
|
|
|
via_bot_user_id = UserId(message->via_bot_id_);
|
|
|
|
if (!via_bot_user_id.is_valid()) {
|
|
|
|
LOG(ERROR) << "Receive invalid " << via_bot_user_id << " from " << source;
|
|
|
|
via_bot_user_id = UserId();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto media_album_id = message->grouped_id_;
|
|
|
|
|
|
|
|
MessageSelfDestructType ttl;
|
|
|
|
bool disable_web_page_preview = false;
|
|
|
|
auto 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(), 0, media_album_id != 0, source),
|
|
|
|
std::move(message->media_), my_dialog_id, message->date_, true, via_bot_user_id, &ttl,
|
|
|
|
&disable_web_page_preview, source);
|
|
|
|
|
|
|
|
auto reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), my_dialog_id, message_id, -1, false);
|
|
|
|
if (reply_header.story_full_id_ != StoryFullId()) {
|
|
|
|
LOG(ERROR) << "Receive reply to " << reply_header.story_full_id_;
|
|
|
|
reply_header.story_full_id_ = {};
|
|
|
|
}
|
2024-02-21 20:27:46 +01:00
|
|
|
if (reply_header.replied_message_info_.is_external() ||
|
|
|
|
reply_header.replied_message_info_.get_reply_message_full_id(DialogId(), true).get_dialog_id() !=
|
|
|
|
DialogId()) {
|
|
|
|
LOG(ERROR) << "Receive reply to " << reply_header.replied_message_info_;
|
|
|
|
reply_header.replied_message_info_ = {};
|
|
|
|
}
|
|
|
|
auto reply_to_message_id = reply_header.replied_message_info_.get_same_chat_reply_to_message_id(true);
|
2024-02-21 20:06:07 +01:00
|
|
|
|
|
|
|
if (!ttl.is_empty()) {
|
2024-02-21 22:48:05 +01:00
|
|
|
LOG(ERROR) << "Wrong " << ttl << " received in " << message_id << " from " << source;
|
2024-02-21 20:06:07 +01:00
|
|
|
}
|
|
|
|
|
2024-02-21 22:48:05 +01:00
|
|
|
auto content_type = content->get_type();
|
2024-02-21 20:06:07 +01:00
|
|
|
if (is_expired_message_content(content_type)) {
|
|
|
|
LOG(ERROR) << "Receive " << content_type << " from " << source;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto result = make_unique<QuickReplyMessage>();
|
2024-02-22 15:32:12 +01:00
|
|
|
result->shortcut_id = message->quick_reply_shortcut_id_;
|
2024-02-21 20:06:07 +01:00
|
|
|
result->message_id = message_id;
|
|
|
|
result->disable_web_page_preview = disable_web_page_preview;
|
|
|
|
result->forward_info = MessageForwardInfo::get_message_forward_info(td_, std::move(forward_header));
|
2024-02-21 20:27:46 +01:00
|
|
|
result->reply_to_message_id = reply_to_message_id;
|
2024-02-21 20:06:07 +01:00
|
|
|
result->via_bot_user_id = via_bot_user_id;
|
|
|
|
result->disable_notification = message->silent_;
|
|
|
|
result->legacy_layer = (message->legacy_ ? MTPROTO_LAYER : 0);
|
|
|
|
result->invert_media = message->invert_media_;
|
|
|
|
result->content = std::move(content);
|
|
|
|
|
|
|
|
if (media_album_id != 0) {
|
|
|
|
if (!is_allowed_media_group_content(content_type)) {
|
|
|
|
if (content_type != MessageContentType::Unsupported) {
|
|
|
|
LOG(ERROR) << "Receive media group identifier " << media_album_id << " in " << message_id
|
|
|
|
<< " with content "
|
|
|
|
<< oneline(to_string(get_quick_reply_message_message_content_object(result.get())));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result->media_album_id = media_album_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Dependencies dependencies;
|
|
|
|
add_quick_reply_message_dependencies(dependencies, result.get());
|
|
|
|
for (auto dependent_dialog_id : dependencies.get_dialog_ids()) {
|
|
|
|
td_->dialog_manager_->force_create_dialog(dependent_dialog_id, source, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
case telegram_api::messageService::ID:
|
|
|
|
LOG(ERROR) << "Receive " << to_string(message_ptr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-02-21 20:27:46 +01:00
|
|
|
void QuickReplyManager::add_quick_reply_message_dependencies(Dependencies &dependencies,
|
|
|
|
const QuickReplyMessage *m) const {
|
2024-02-21 20:06:07 +01:00
|
|
|
auto is_bot = td_->auth_manager_->is_bot();
|
|
|
|
dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id);
|
|
|
|
dependencies.add(m->via_bot_user_id);
|
|
|
|
if (m->forward_info != nullptr) {
|
|
|
|
m->forward_info->add_dependencies(dependencies);
|
|
|
|
}
|
|
|
|
add_message_content_dependencies(dependencies, m->content.get(), is_bot);
|
|
|
|
}
|
|
|
|
|
2024-02-21 20:43:29 +01:00
|
|
|
bool QuickReplyManager::can_resend_message(const QuickReplyMessage *m) const {
|
|
|
|
if (m->send_error_code != 429) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (m->forward_info != nullptr || m->real_forward_from_dialog_id.is_valid()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (m->via_bot_user_id.is_valid() || m->hide_via_bot) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::MessageSendingState> QuickReplyManager::get_message_sending_state_object(
|
|
|
|
const QuickReplyMessage *m) const {
|
|
|
|
CHECK(m != nullptr);
|
|
|
|
if (m->message_id.is_yet_unsent()) {
|
|
|
|
return td_api::make_object<td_api::messageSendingStatePending>(m->sending_id);
|
|
|
|
}
|
|
|
|
if (m->is_failed_to_send) {
|
|
|
|
auto can_retry = can_resend_message(m);
|
|
|
|
auto error_code = m->send_error_code > 0 ? m->send_error_code : 400;
|
|
|
|
auto need_another_reply_quote =
|
|
|
|
can_retry && error_code == 400 && m->send_error_message == CSlice("QUOTE_TEXT_INVALID");
|
|
|
|
return td_api::make_object<td_api::messageSendingStateFailed>(
|
|
|
|
td_api::make_object<td_api::error>(error_code, m->send_error_message), can_retry, false,
|
|
|
|
need_another_reply_quote, false, max(m->try_resend_at - Time::now(), 0.0));
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::MessageContent> QuickReplyManager::get_quick_reply_message_message_content_object(
|
|
|
|
const QuickReplyMessage *m) const {
|
2024-02-21 22:48:05 +01:00
|
|
|
return get_message_content_object(m->content.get(), td_, DialogId(), 0, false, true, -1, m->invert_media,
|
|
|
|
m->disable_web_page_preview);
|
2024-02-21 20:43:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::quickReplyMessage> QuickReplyManager::get_quick_reply_message_object(
|
|
|
|
const QuickReplyMessage *m, const char *source) const {
|
|
|
|
CHECK(m != nullptr);
|
|
|
|
auto forward_info =
|
|
|
|
m->forward_info == nullptr ? nullptr : m->forward_info->get_message_forward_info_object(td_, false);
|
|
|
|
return td_api::make_object<td_api::quickReplyMessage>(
|
|
|
|
m->message_id.get(), get_message_sending_state_object(m), std::move(forward_info), m->reply_to_message_id.get(),
|
|
|
|
td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"), m->media_album_id,
|
|
|
|
get_quick_reply_message_message_content_object(m));
|
|
|
|
}
|
|
|
|
|
2024-02-22 15:32:12 +01:00
|
|
|
td_api::object_ptr<td_api::quickReplyShortcut> QuickReplyManager::get_quick_reply_shortcut_object(
|
|
|
|
const Shortcut *s, const char *source) const {
|
|
|
|
CHECK(s != nullptr);
|
|
|
|
CHECK(!s->messages_.empty());
|
|
|
|
return td_api::make_object<td_api::quickReplyShortcut>(
|
|
|
|
s->name_, get_quick_reply_message_object(s->messages_[0].get(), source), s->total_count_);
|
|
|
|
}
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::quickReplyShortcuts> QuickReplyManager::get_quick_reply_shortcuts_object(
|
|
|
|
const char *source) const {
|
|
|
|
CHECK(shortcuts_.are_inited_);
|
|
|
|
return td_api::make_object<td_api::quickReplyShortcuts>(
|
|
|
|
transform(shortcuts_.shortcuts_, [this, source](const unique_ptr<Shortcut> &shortcut) {
|
|
|
|
return get_quick_reply_shortcut_object(shortcut.get(), source);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::get_quick_reply_shortcuts(Promise<td_api::object_ptr<td_api::quickReplyShortcuts>> &&promise) {
|
|
|
|
if (shortcuts_.are_inited_) {
|
|
|
|
return promise.set_value(get_quick_reply_shortcuts_object("get_quick_reply_shortcuts"));
|
|
|
|
}
|
|
|
|
|
|
|
|
load_quick_reply_shortcuts(std::move(promise));
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::load_quick_reply_shortcuts(Promise<td_api::object_ptr<td_api::quickReplyShortcuts>> &&promise) {
|
|
|
|
shortcuts_.load_queries_.push_back(std::move(promise));
|
|
|
|
if (shortcuts_.load_queries_.size() != 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
reload_quick_reply_shortcuts();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::reload_quick_reply_shortcuts() {
|
|
|
|
auto promise = PromiseCreator::lambda(
|
|
|
|
[actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::messages_QuickReplies>> r_shortcuts) {
|
|
|
|
send_closure(actor_id, &QuickReplyManager::on_reload_quick_reply_shortcuts, std::move(r_shortcuts));
|
|
|
|
});
|
|
|
|
td_->create_handler<GetQuickRepliesQuery>(std::move(promise))->send(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::on_reload_quick_reply_shortcuts(
|
|
|
|
Result<telegram_api::object_ptr<telegram_api::messages_QuickReplies>> r_shortcuts) {
|
|
|
|
G()->ignore_result_if_closing(r_shortcuts);
|
|
|
|
if (r_shortcuts.is_error()) {
|
|
|
|
return on_load_quick_reply_fail(r_shortcuts.move_as_error());
|
|
|
|
}
|
|
|
|
auto shortcuts_ptr = r_shortcuts.move_as_ok();
|
|
|
|
switch (shortcuts_ptr->get_id()) {
|
|
|
|
case telegram_api::messages_quickRepliesNotModified::ID:
|
|
|
|
if (!shortcuts_.are_inited_) {
|
|
|
|
shortcuts_.are_inited_ = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case telegram_api::messages_quickReplies::ID: {
|
|
|
|
auto shortcuts = telegram_api::move_object_as<telegram_api::messages_quickReplies>(shortcuts_ptr);
|
|
|
|
td_->contacts_manager_->on_get_users(std::move(shortcuts->users_), "messages.quickReplies");
|
|
|
|
td_->contacts_manager_->on_get_chats(std::move(shortcuts->chats_), "messages.quickReplies");
|
|
|
|
|
|
|
|
FlatHashMap<MessageId, telegram_api::object_ptr<telegram_api::Message>, MessageIdHash> message_id_to_message;
|
|
|
|
for (auto &message : shortcuts->messages_) {
|
|
|
|
auto message_id = MessageId::get_message_id(message, false);
|
|
|
|
if (!message_id.is_valid()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
message_id_to_message[message_id] = std::move(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
FlatHashSet<int32> added_shortcut_ids;
|
|
|
|
FlatHashSet<string> added_shortcut_names;
|
|
|
|
vector<unique_ptr<Shortcut>> new_shortcuts;
|
|
|
|
for (auto &quick_reply : shortcuts->quick_replies_) {
|
|
|
|
if (quick_reply->shortcut_id_ <= 0 || quick_reply->shortcut_.empty() || quick_reply->count_ <= 0 ||
|
|
|
|
quick_reply->top_message_ <= 0) {
|
|
|
|
LOG(ERROR) << "Receive " << to_string(quick_reply);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (added_shortcut_ids.count(quick_reply->shortcut_id_) || added_shortcut_names.count(quick_reply->shortcut_)) {
|
|
|
|
LOG(ERROR) << "Receive duplicate " << to_string(quick_reply);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
added_shortcut_ids.insert(quick_reply->shortcut_id_);
|
|
|
|
added_shortcut_names.insert(quick_reply->shortcut_);
|
|
|
|
|
|
|
|
MessageId first_message_id(ServerMessageId(quick_reply->top_message_));
|
|
|
|
auto it = message_id_to_message.find(first_message_id);
|
|
|
|
if (it == message_id_to_message.end()) {
|
|
|
|
LOG(ERROR) << "Can't find last " << first_message_id << " in shortcut " << quick_reply->shortcut_;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto message = create_message(std::move(it->second), "on_reload_quick_reply_shortcuts");
|
|
|
|
message_id_to_message.erase(it);
|
|
|
|
if (message == nullptr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (message->shortcut_id != quick_reply->shortcut_id_) {
|
|
|
|
LOG(ERROR) << "Receive message from shortcut " << message->shortcut_id << " instead of "
|
|
|
|
<< quick_reply->shortcut_id_;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto shortcut = td::make_unique<Shortcut>();
|
|
|
|
shortcut->name_ = std::move(quick_reply->shortcut_);
|
|
|
|
shortcut->shortcut_id_ = quick_reply->shortcut_id_;
|
|
|
|
shortcut->total_count_ = quick_reply->count_;
|
|
|
|
shortcut->messages_.push_back(std::move(message));
|
|
|
|
new_shortcuts.push_back(std::move(shortcut));
|
|
|
|
}
|
|
|
|
shortcuts_.shortcuts_ = std::move(new_shortcuts);
|
|
|
|
shortcuts_.are_inited_ = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
on_load_quick_reply_success();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::on_load_quick_reply_success() {
|
|
|
|
auto promises = std::move(shortcuts_.load_queries_);
|
|
|
|
reset_to_empty(shortcuts_.load_queries_);
|
|
|
|
for (auto &promise : promises) {
|
|
|
|
if (promise) {
|
|
|
|
promise.set_value(get_quick_reply_shortcuts_object("on_load_quick_reply_success"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QuickReplyManager::on_load_quick_reply_fail(Status error) {
|
|
|
|
fail_promises(shortcuts_.load_queries_, std::move(error));
|
|
|
|
}
|
|
|
|
|
2024-02-21 18:43:55 +01:00
|
|
|
} // namespace td
|