diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 4755dd169..69561a926 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -7818,6 +7818,17 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok; +//@description Sends a business message; for bots only. Returns the message after it was sent +//@business_connection_id Unique identifier of business connection on behalf of which to send the request +//@chat_id Target chat +//@reply_to Information about the message; pass null if none +//@disable_notification Pass true to disable notification for the message +//@protect_content Pass true if the content of the message must be protected from forwarding and saving; for bots only +//@reply_markup Markup for replying to the message; pass null if none; for bots only +//@input_message_content The content of the message to be sent +sendBusinessMessage business_connection_id:string chat_id:int53 reply_to:InputMessageReplyTo disable_notification:Bool protect_content:Bool reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; + + //@description Checks validness of a name for a quick reply shortcut. Can be called synchronously @name The name of the shortcut; 1-32 characters checkQuickReplyShortcutName name:string = Ok; diff --git a/td/telegram/BusinessConnectionManager.cpp b/td/telegram/BusinessConnectionManager.cpp index 52300f32f..2a3a8450d 100644 --- a/td/telegram/BusinessConnectionManager.cpp +++ b/td/telegram/BusinessConnectionManager.cpp @@ -10,7 +10,14 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageCopyOptions.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/ReplyMarkup.h" +#include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -18,6 +25,7 @@ #include "td/utils/format.h" #include "td/utils/logging.h" +#include "td/utils/Random.h" namespace td { @@ -83,6 +91,95 @@ struct BusinessConnectionManager::BusinessConnection { } }; +struct BusinessConnectionManager::PendingMessage { + BusinessConnectionId business_connection_id_; + DialogId dialog_id_; + MessageInputReplyTo input_reply_to_; + string send_emoji_; + MessageSelfDestructType ttl_; + unique_ptr content_; + unique_ptr reply_markup_; + int64 media_album_id_ = 0; + int64 random_id_ = 0; + bool noforwards_ = false; + bool disable_notification_ = false; + bool invert_media_ = false; + bool disable_web_page_preview_ = false; +}; + +class BusinessConnectionManager::SendBusinessMessageQuery final : public Td::ResultHandler { + Promise> promise_; + unique_ptr message_; + + public: + explicit SendBusinessMessageQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(unique_ptr message) { + message_ = std::move(message); + + int32 flags = 0; + if (message_->disable_web_page_preview_) { + flags |= telegram_api::messages_sendMessage::NO_WEBPAGE_MASK; + } + if (message_->disable_notification_) { + flags |= telegram_api::messages_sendMessage::SILENT_MASK; + } + if (message_->noforwards_) { + flags |= telegram_api::messages_sendMessage::NOFORWARDS_MASK; + } + if (message_->invert_media_) { + flags |= telegram_api::messages_sendMessage::INVERT_MEDIA_MASK; + } + + auto input_peer = td_->dialog_manager_->get_input_peer_force(message_->dialog_id_); + CHECK(input_peer != nullptr); + + auto reply_to = message_->input_reply_to_.get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; + } + + const FormattedText *message_text = get_message_content_text(message_->content_.get()); + CHECK(message_text != nullptr); + auto entities = get_input_message_entities(td_->contacts_manager_.get(), message_text, "SendBusinessMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMessage::ENTITIES_MASK; + } + + if (message_->reply_markup_ != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_MARKUP_MASK; + } + + send_query(G()->net_query_creator().create_with_prefix( + message_->business_connection_id_.get_invoke_prefix(), + telegram_api::messages_sendMessage( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), message_text->text, + message_->random_id_, get_input_reply_markup(td_->contacts_manager_.get(), message_->reply_markup_), + std::move(entities), 0, nullptr, nullptr), + td_->business_connection_manager_->get_business_connection_dc_id(message_->business_connection_id_), + {{message_->dialog_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(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 SendBusinessMessageQuery: " << to_string(ptr); + promise_.set_value(nullptr); // TODO + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendBusinessMessageQuery: " << status; + promise_.set_error(std::move(status)); + } +}; + BusinessConnectionManager::BusinessConnectionManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } @@ -259,4 +356,119 @@ void BusinessConnectionManager::on_get_business_connection( } } +MessageInputReplyTo BusinessConnectionManager::create_business_message_input_reply_to( + td_api::object_ptr &&reply_to) { + if (reply_to == nullptr) { + return {}; + } + switch (reply_to->get_id()) { + case td_api::inputMessageReplyToStory::ID: + return {}; + case td_api::inputMessageReplyToMessage::ID: { + auto reply_to_message = td_api::move_object_as(reply_to); + auto message_id = MessageId(reply_to_message->message_id_); + if (!message_id.is_valid() || !message_id.is_server()) { + return {}; + } + if (reply_to_message->chat_id_ != 0) { + return {}; + } + FormattedText quote; + int32 quote_position = 0; + if (reply_to_message->quote_ != nullptr) { + int32 ltrim_count = 0; + auto r_quote = get_formatted_text(td_, td_->dialog_manager_->get_my_dialog_id(), + std::move(reply_to_message->quote_->text_), td_->auth_manager_->is_bot(), + true, true, false, <rim_count); + if (r_quote.is_ok() && !r_quote.ok().text.empty()) { + quote = r_quote.move_as_ok(); + quote_position = reply_to_message->quote_->position_; + if (0 <= quote_position && quote_position <= 1000000) { // some unreasonably big bound + quote_position += ltrim_count; + } else { + quote_position = 0; + } + } + } + return MessageInputReplyTo{message_id, DialogId(), std::move(quote), quote_position}; + } + default: + UNREACHABLE(); + return {}; + } +} + +Result BusinessConnectionManager::process_input_message_content( + DialogId dialog_id, td_api::object_ptr &&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) { + return Status::Error(400, "Can't forward messages as business"); + } + return get_input_message_content(dialog_id, std::move(input_message_content), td_, true); +} + +unique_ptr BusinessConnectionManager::create_business_message_to_send( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageInputReplyTo &&input_reply_to, + bool disable_notification, bool protect_content, unique_ptr &&reply_markup, + InputMessageContent &&input_content) const { + auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), input_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + auto message = make_unique(); + message->business_connection_id_ = business_connection_id; + message->dialog_id_ = dialog_id; + message->input_reply_to_ = std::move(input_reply_to); + message->noforwards_ = protect_content; + message->content_ = std::move(content); + message->reply_markup_ = std::move(reply_markup); + message->disable_notification_ = disable_notification; + message->invert_media_ = input_content.invert_media; + message->disable_web_page_preview_ = input_content.disable_web_page_preview; + message->ttl_ = input_content.ttl; + message->send_emoji_ = std::move(input_content.emoji); + message->random_id_ = Random::secure_int64(); + return message; +} + +void BusinessConnectionManager::send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, + bool disable_notification, bool protect_content, + td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_RESULT_PROMISE(promise, input_content, + process_input_message_content(dialog_id, std::move(input_message_content))); + auto input_reply_to = create_business_message_input_reply_to(std::move(reply_to)); + TRY_RESULT_PROMISE(promise, message_reply_markup, + get_reply_markup(std::move(reply_markup), DialogType::User, td_->auth_manager_->is_bot(), false)); + + auto message = create_business_message_to_send(std::move(business_connection_id), dialog_id, + std::move(input_reply_to), disable_notification, protect_content, + std::move(message_reply_markup), std::move(input_content)); + + do_send_message(std::move(message), std::move(promise)); +} + +void BusinessConnectionManager::do_send_message(unique_ptr &&message, + Promise> &&promise) { + LOG(INFO) << "Send business message to " << message->dialog_id_; + + auto content = message->content_.get(); + CHECK(content != nullptr); + auto content_type = content->get_type(); + if (content_type == MessageContentType::Text) { + auto input_media = get_message_content_input_media_web_page(td_, content); + if (input_media == nullptr) { + td_->create_handler(std::move(promise))->send(std::move(message)); + } else { + promise.set_error(Status::Error(400, "Unsupported")); + } + return; + } + + promise.set_error(Status::Error(400, "Unsupported")); +} + } // namespace td diff --git a/td/telegram/BusinessConnectionManager.h b/td/telegram/BusinessConnectionManager.h index c420a492a..e06120aad 100644 --- a/td/telegram/BusinessConnectionManager.h +++ b/td/telegram/BusinessConnectionManager.h @@ -8,6 +8,7 @@ #include "td/telegram/BusinessConnectionId.h" #include "td/telegram/DialogId.h" +#include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/net/DcId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -21,6 +22,8 @@ namespace td { +struct InputMessageContent; +struct ReplyMarkup; class Td; class BusinessConnectionManager final : public Actor { @@ -50,14 +53,36 @@ class BusinessConnectionManager final : public Actor { void get_business_connection(const BusinessConnectionId &connection_id, Promise> &&promise); + void send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, bool disable_notification, + bool protect_content, td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise); + private: struct BusinessConnection; + struct PendingMessage; + class SendBusinessMessageQuery; void tear_down() final; void on_get_business_connection(const BusinessConnectionId &connection_id, Result> r_updates); + MessageInputReplyTo create_business_message_input_reply_to( + td_api::object_ptr &&reply_to); + + Result process_input_message_content( + DialogId dialog_id, td_api::object_ptr &&input_message_content); + + unique_ptr create_business_message_to_send(BusinessConnectionId business_connection_id, + DialogId dialog_id, MessageInputReplyTo &&input_reply_to, + bool disable_notification, bool protect_content, + unique_ptr &&reply_markup, + InputMessageContent &&input_content) const; + + void do_send_message(unique_ptr &&message, Promise> &&promise); + WaitFreeHashMap, BusinessConnectionIdHash> business_connections_; FlatHashMap>>, diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 496e8c848..6c5d409e0 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -24486,15 +24486,16 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->initial_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, "do_send_message"), message_text->text, - m->is_copy, random_id, &m->send_query_ref); + get_input_message_entities(td_->contacts_manager_.get(), message_text, "on_text_message_ready_to_send"), + message_text->text, m->is_copy, random_id, &m->send_query_ref); } else { td_->create_handler()->send( FileId(), FileId(), get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->initial_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, "do_send_message"), message_text->text, - std::move(input_media), MessageContentType::Text, m->is_copy, random_id, &m->send_query_ref); + get_input_message_entities(td_->contacts_manager_.get(), message_text, "on_text_message_ready_to_send"), + message_text->text, std::move(input_media), MessageContentType::Text, m->is_copy, random_id, + &m->send_query_ref); } } } diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 9abcd152e..b079535e9 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5789,6 +5789,15 @@ void Td::on_request(uint64 id, td_api::editMessageSchedulingState &request) { std::move(request.scheduling_state_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::sendBusinessMessage &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->send_message( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + std::move(request.reply_to_), request.disable_notification_, request.protect_content_, + std::move(request.reply_markup_), std::move(request.input_message_content_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index c589c1748..8c1d23181 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -876,6 +876,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::editMessageSchedulingState &request); + void on_request(uint64 id, td_api::sendBusinessMessage &request); + void on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request); void on_request(uint64 id, const td_api::setQuickReplyShortcutName &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index a2991e648..fd964d973 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2343,6 +2343,12 @@ class CliClient final : public Actor { void send_message(int64 chat_id, td_api::object_ptr &&input_message_content, bool disable_notification = false, bool from_background = false) { + if (!business_connection_id_.empty()) { + send_request(td_api::make_object( + business_connection_id_, chat_id, get_input_message_reply_to(), disable_notification, rand_bool(), nullptr, + std::move(input_message_content))); + return; + } auto id = send_request(td_api::make_object( chat_id, message_thread_id_, get_input_message_reply_to(), td_api::make_object(disable_notification, from_background, true, true,