Add td_api::sendBusinessMessageAlbum.

This commit is contained in:
levlam 2024-03-13 17:44:00 +03:00
parent 1b448b93e5
commit 74b62ccbf4
6 changed files with 244 additions and 18 deletions

View File

@ -7673,7 +7673,8 @@ setChatMessageSender chat_id:int53 message_sender_id:MessageSender = Ok;
//@input_message_content The content of the message to be sent //@input_message_content The content of the message to be sent
sendMessage chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; sendMessage chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message;
//@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages //@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album.
//-Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages
//@chat_id Target chat //@chat_id Target chat
//@message_thread_id If not 0, the message thread identifier in which the messages will be sent //@message_thread_id If not 0, the message thread identifier in which the messages will be sent
//@reply_to Information about the message or story to be replied; pass null if none //@reply_to Information about the message or story to be replied; pass null if none
@ -7818,16 +7819,26 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup =
editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok; 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 //@description Sends a message on behalf of a business account; 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 //@business_connection_id Unique identifier of business connection on behalf of which to send the request
//@chat_id Target chat //@chat_id Target chat
//@reply_to Information about the message; pass null if none //@reply_to Information about the message to be replied; pass null if none
//@disable_notification Pass true to disable notification for the message //@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 //@protect_content Pass true if the content of the message must be protected from forwarding and saving
//@reply_markup Markup for replying to the message; pass null if none; for bots only //@reply_markup Markup for replying to the message; pass null if none
//@input_message_content The content of the message to be sent //@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; 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 Sends 2-10 messages grouped together into an album on behalf of a business account; for bots only. Currently, only audio, document, photo and video messages can be grouped into an album.
//-Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages
//@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 to be replied; 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
//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album
sendBusinessMessageAlbum business_connection_id:string chat_id:int53 reply_to:InputMessageReplyTo disable_notification:Bool protect_content:Bool input_message_contents:vector<InputMessageContent> = Messages;
//@description Checks validness of a name for a quick reply shortcut. Can be called synchronously @name The name of the shortcut; 1-32 characters //@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; checkQuickReplyShortcutName name:string = Ok;

View File

@ -15,6 +15,7 @@
#include "td/telegram/files/FileType.h" #include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h" #include "td/telegram/Global.h"
#include "td/telegram/MessageContent.h" #include "td/telegram/MessageContent.h"
#include "td/telegram/MessageContentType.h"
#include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageCopyOptions.h"
#include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h" #include "td/telegram/MessageId.h"
@ -103,7 +104,6 @@ struct BusinessConnectionManager::PendingMessage {
MessageSelfDestructType ttl_; MessageSelfDestructType ttl_;
unique_ptr<MessageContent> content_; unique_ptr<MessageContent> content_;
unique_ptr<ReplyMarkup> reply_markup_; unique_ptr<ReplyMarkup> reply_markup_;
int64 media_album_id_ = 0;
int64 random_id_ = 0; int64 random_id_ = 0;
bool noforwards_ = false; bool noforwards_ = false;
bool disable_notification_ = false; bool disable_notification_ = false;
@ -255,6 +255,66 @@ class BusinessConnectionManager::SendBusinessMediaQuery final : public Td::Resul
} }
}; };
class BusinessConnectionManager::SendBusinessMultiMediaQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::messages>> promise_;
vector<unique_ptr<PendingMessage>> messages_;
public:
explicit SendBusinessMultiMediaQuery(Promise<td_api::object_ptr<td_api::messages>> &&promise)
: promise_(std::move(promise)) {
}
void send(vector<unique_ptr<PendingMessage>> &&messages,
vector<telegram_api::object_ptr<telegram_api::inputSingleMedia>> &&input_single_media) {
CHECK(!messages.empty());
messages_ = std::move(messages);
int32 flags = 0;
if (messages_[0]->disable_notification_) {
flags |= telegram_api::messages_sendMultiMedia::SILENT_MASK;
}
if (messages_[0]->noforwards_) {
flags |= telegram_api::messages_sendMultiMedia::NOFORWARDS_MASK;
}
if (messages_[0]->invert_media_) {
flags |= telegram_api::messages_sendMultiMedia::INVERT_MEDIA_MASK;
}
auto input_peer = td_->dialog_manager_->get_input_peer(messages_[0]->dialog_id_, AccessRights::Know);
CHECK(input_peer != nullptr);
auto reply_to = messages_[0]->input_reply_to_.get_input_reply_to(td_, MessageId());
if (reply_to != nullptr) {
flags |= telegram_api::messages_sendMultiMedia::REPLY_TO_MASK;
}
send_query(G()->net_query_creator().create_with_prefix(
messages_[0]->business_connection_id_.get_invoke_prefix(),
telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, false /*ignored*/, false /*ignored*/,
std::move(input_peer), std::move(reply_to), std::move(input_single_media),
0, nullptr, nullptr),
td_->business_connection_manager_->get_business_connection_dc_id(messages_[0]->business_connection_id_),
{{messages_[0]->dialog_id_}}));
}
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 SendBusinessMultiMediaQuery: " << to_string(ptr);
promise_.set_value(nullptr); // TODO
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for SendBusinessMultiMediaQuery: " << status;
promise_.set_error(std::move(status));
}
};
class BusinessConnectionManager::UploadBusinessMediaQuery final : public Td::ResultHandler { class BusinessConnectionManager::UploadBusinessMediaQuery final : public Td::ResultHandler {
Promise<UploadMediaResult> promise_; Promise<UploadMediaResult> promise_;
unique_ptr<PendingMessage> message_; unique_ptr<PendingMessage> message_;
@ -661,6 +721,10 @@ void BusinessConnectionManager::do_send_message(unique_ptr<PendingMessage> &&mes
td_->create_handler<SendBusinessMediaQuery>(std::move(promise))->send(std::move(message), std::move(input_media)); td_->create_handler<SendBusinessMediaQuery>(std::move(promise))->send(std::move(message), std::move(input_media));
return; return;
} }
if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll ||
content_type == MessageContentType::Story) {
return promise.set_error(Status::Error(400, "Message has no file"));
}
upload_media(std::move(message), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( upload_media(std::move(message), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
Result<UploadMediaResult> &&result) mutable { Result<UploadMediaResult> &&result) mutable {
if (result.is_error()) { if (result.is_error()) {
@ -691,17 +755,23 @@ FileId BusinessConnectionManager::get_message_thumbnail_file_id(const unique_ptr
void BusinessConnectionManager::upload_media(unique_ptr<PendingMessage> &&message, Promise<UploadMediaResult> &&promise, void BusinessConnectionManager::upload_media(unique_ptr<PendingMessage> &&message, Promise<UploadMediaResult> &&promise,
vector<int> bad_parts) { vector<int> bad_parts) {
auto content_type = message->content_->get_type(); auto file_id = get_message_file_id(message);
if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll || FileView file_view = td_->file_manager_->get_file_view(file_id);
content_type == MessageContentType::Story) { if (file_view.is_encrypted()) {
return promise.set_error(Status::Error(400, "Message has no file")); return promise.set_error(Status::Error(400, "Can't use encrypted file"));
}
if (file_view.has_remote_location() && file_view.main_remote_location().is_web()) {
return promise.set_error(Status::Error(400, "Can't use a web file"));
} }
BeingUploadedMedia media; BeingUploadedMedia media;
media.message_ = std::move(message); media.message_ = std::move(message);
media.promise_ = std::move(promise); media.promise_ = std::move(promise);
auto file_id = get_message_file_id(media.message_); if (!file_view.has_remote_location() && file_view.has_url()) {
return do_upload_media(std::move(media), nullptr);
}
LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts;
CHECK(file_id.is_valid()); CHECK(file_id.is_valid());
bool is_inserted = being_uploaded_files_.emplace(file_id, std::move(media)).second; bool is_inserted = being_uploaded_files_.emplace(file_id, std::move(media)).second;
@ -784,8 +854,7 @@ void BusinessConnectionManager::do_upload_media(BeingUploadedMedia &&being_uploa
file_id, thumbnail_file_id, message->ttl_, message->send_emoji_, true); file_id, thumbnail_file_id, message->ttl_, message->send_emoji_, true);
CHECK(input_media != nullptr); CHECK(input_media != nullptr);
auto input_media_id = input_media->get_id(); auto input_media_id = input_media->get_id();
if (!have_input_file || input_media_id == telegram_api::inputMediaDocument::ID || if (input_media_id == telegram_api::inputMediaDocument::ID || input_media_id == telegram_api::inputMediaPhoto::ID) {
input_media_id == telegram_api::inputMediaPhoto::ID) {
// can use input media directly // can use input media directly
UploadMediaResult result; UploadMediaResult result;
result.message_ = std::move(being_uploaded_media.message_); result.message_ = std::move(being_uploaded_media.message_);
@ -880,4 +949,104 @@ void BusinessConnectionManager::complete_upload_media(unique_ptr<PendingMessage>
promise.set_value(std::move(result)); promise.set_value(std::move(result));
} }
void BusinessConnectionManager::send_message_album(
BusinessConnectionId business_connection_id, DialogId dialog_id,
td_api::object_ptr<td_api::InputMessageReplyTo> &&reply_to, bool disable_notification, bool protect_content,
vector<td_api::object_ptr<td_api::InputMessageContent>> &&input_message_contents,
Promise<td_api::object_ptr<td_api::messages>> &&promise) {
if (input_message_contents.size() > MAX_GROUPED_MESSAGES) {
return promise.set_error(Status::Error(400, "Too many messages to send as an album"));
}
if (input_message_contents.empty()) {
return promise.set_error(Status::Error(400, "There are no messages to send"));
}
TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id));
vector<InputMessageContent> message_contents;
std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
for (auto &input_message_content : input_message_contents) {
TRY_RESULT_PROMISE(promise, message_content,
process_input_message_content(dialog_id, std::move(input_message_content)));
auto message_content_type = message_content.content->get_type();
if (!is_allowed_media_group_content(message_content_type)) {
return promise.set_error(Status::Error(400, "Invalid message content type"));
}
message_content_types.insert(message_content_type);
message_contents.push_back(std::move(message_content));
}
if (message_content_types.size() > 1) {
for (auto message_content_type : message_content_types) {
if (is_homogenous_media_group_content(message_content_type)) {
return promise.set_error(
Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types"));
}
}
}
auto input_reply_to = create_business_message_input_reply_to(std::move(reply_to));
auto request_id = ++current_media_group_send_request_id_;
auto &request = media_group_send_requests_[request_id];
request.upload_results_.resize(message_contents.size());
request.promise_ = std::move(promise);
for (size_t media_pos = 0; media_pos < message_contents.size(); media_pos++) {
auto &message_content = message_contents[media_pos];
auto message =
create_business_message_to_send(business_connection_id, dialog_id, input_reply_to.clone(), disable_notification,
protect_content, nullptr, std::move(message_content));
upload_media(std::move(message), PromiseCreator::lambda([actor_id = actor_id(this), request_id,
media_pos](Result<UploadMediaResult> &&result) mutable {
send_closure(actor_id, &BusinessConnectionManager::on_upload_message_album_media, request_id,
media_pos, std::move(result));
}));
}
}
void BusinessConnectionManager::on_upload_message_album_media(int64 request_id, size_t media_pos,
Result<UploadMediaResult> &&result) {
auto it = media_group_send_requests_.find(request_id);
CHECK(it != media_group_send_requests_.end());
auto &request = it->second;
request.upload_results_[media_pos] = std::move(result);
request.finished_count_++;
LOG(INFO) << "Receive uploaded media " << media_pos << " for request " << request_id;
if (request.finished_count_ != request.upload_results_.size()) {
return;
}
auto upload_results = std::move(request.upload_results_);
auto promise = std::move(request.promise_);
media_group_send_requests_.erase(it);
TRY_STATUS_PROMISE(promise, G()->close_status());
for (auto &r_upload_result : upload_results) {
if (r_upload_result.is_error()) {
return promise.set_error(r_upload_result.move_as_error());
}
}
vector<unique_ptr<PendingMessage>> messages;
vector<telegram_api::object_ptr<telegram_api::inputSingleMedia>> input_single_media;
for (auto &r_upload_result : upload_results) {
auto upload_result = r_upload_result.move_as_ok();
auto message = std::move(upload_result.message_);
int32 flags = 0;
const FormattedText *caption = get_message_content_text(message->content_.get());
auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "on_upload_message_album_media");
if (!entities.empty()) {
flags |= telegram_api::inputSingleMedia::ENTITIES_MASK;
}
input_single_media.push_back(telegram_api::make_object<telegram_api::inputSingleMedia>(
flags, std::move(upload_result.input_media_), message->random_id_,
caption == nullptr ? string() : caption->text, std::move(entities)));
messages.push_back(std::move(message));
}
td_->create_handler<SendBusinessMultiMediaQuery>(std::move(promise))
->send(std::move(messages), std::move(input_single_media));
}
} // namespace td } // namespace td

View File

@ -63,11 +63,20 @@ class BusinessConnectionManager final : public Actor {
td_api::object_ptr<td_api::InputMessageContent> &&input_message_content, td_api::object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<td_api::object_ptr<td_api::message>> &&promise); Promise<td_api::object_ptr<td_api::message>> &&promise);
void send_message_album(BusinessConnectionId business_connection_id, DialogId dialog_id,
td_api::object_ptr<td_api::InputMessageReplyTo> &&reply_to, bool disable_notification,
bool protect_content,
vector<td_api::object_ptr<td_api::InputMessageContent>> &&input_message_contents,
Promise<td_api::object_ptr<td_api::messages>> &&promise);
private: private:
static constexpr size_t MAX_GROUPED_MESSAGES = 10; // server side limit
struct BusinessConnection; struct BusinessConnection;
struct PendingMessage; struct PendingMessage;
class SendBusinessMessageQuery; class SendBusinessMessageQuery;
class SendBusinessMediaQuery; class SendBusinessMediaQuery;
class SendBusinessMultiMediaQuery;
class UploadBusinessMediaQuery; class UploadBusinessMediaQuery;
class UploadMediaCallback; class UploadMediaCallback;
class UploadThumbnailCallback; class UploadThumbnailCallback;
@ -83,6 +92,12 @@ class BusinessConnectionManager final : public Actor {
Promise<UploadMediaResult> promise_; Promise<UploadMediaResult> promise_;
}; };
struct MediaGroupSendRequest {
size_t finished_count_ = 0;
vector<Result<UploadMediaResult>> upload_results_;
Promise<td_api::object_ptr<td_api::messages>> promise_;
};
void tear_down() final; void tear_down() final;
void on_get_business_connection(const BusinessConnectionId &connection_id, void on_get_business_connection(const BusinessConnectionId &connection_id,
@ -127,12 +142,19 @@ class BusinessConnectionManager final : public Actor {
telegram_api::object_ptr<telegram_api::MessageMedia> &&media, telegram_api::object_ptr<telegram_api::MessageMedia> &&media,
Promise<UploadMediaResult> &&promise); Promise<UploadMediaResult> &&promise);
int64 generate_new_media_album_id();
void on_upload_message_album_media(int64 request_id, size_t media_pos, Result<UploadMediaResult> &&result);
WaitFreeHashMap<BusinessConnectionId, unique_ptr<BusinessConnection>, BusinessConnectionIdHash> business_connections_; WaitFreeHashMap<BusinessConnectionId, unique_ptr<BusinessConnection>, BusinessConnectionIdHash> business_connections_;
FlatHashMap<BusinessConnectionId, vector<Promise<td_api::object_ptr<td_api::businessConnection>>>, FlatHashMap<BusinessConnectionId, vector<Promise<td_api::object_ptr<td_api::businessConnection>>>,
BusinessConnectionIdHash> BusinessConnectionIdHash>
get_business_connection_queries_; get_business_connection_queries_;
int64 current_media_group_send_request_id_ = 0;
FlatHashMap<int64, MediaGroupSendRequest> media_group_send_requests_;
std::shared_ptr<UploadMediaCallback> upload_media_callback_; std::shared_ptr<UploadMediaCallback> upload_media_callback_;
std::shared_ptr<UploadThumbnailCallback> upload_thumbnail_callback_; std::shared_ptr<UploadThumbnailCallback> upload_thumbnail_callback_;

View File

@ -5798,6 +5798,15 @@ void Td::on_request(uint64 id, td_api::sendBusinessMessage &request) {
std::move(request.reply_markup_), std::move(request.input_message_content_), std::move(promise)); std::move(request.reply_markup_), std::move(request.input_message_content_), std::move(promise));
} }
void Td::on_request(uint64 id, td_api::sendBusinessMessageAlbum &request) {
CHECK_IS_BOT();
CREATE_REQUEST_PROMISE();
business_connection_manager_->send_message_album(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.input_message_contents_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request) { void Td::on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE(); CREATE_OK_REQUEST_PROMISE();

View File

@ -878,6 +878,8 @@ class Td final : public Actor {
void on_request(uint64 id, td_api::sendBusinessMessage &request); void on_request(uint64 id, td_api::sendBusinessMessage &request);
void on_request(uint64 id, td_api::sendBusinessMessageAlbum &request);
void on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request); void on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request);
void on_request(uint64 id, const td_api::setQuickReplyShortcutName &request); void on_request(uint64 id, const td_api::setQuickReplyShortcutName &request);

View File

@ -4773,9 +4773,15 @@ class CliClient final : public Actor {
rand_bool() ? get_message_self_destruct_type() : nullptr, has_spoiler_ && rand_bool()); rand_bool() ? get_message_self_destruct_type() : nullptr, has_spoiler_ && rand_bool());
return content; return content;
}); });
send_request(td_api::make_object<td_api::sendMessageAlbum>( if (!business_connection_id_.empty()) {
chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(), send_request(td_api::make_object<td_api::sendBusinessMessageAlbum>(
std::move(input_message_contents))); business_connection_id_, chat_id, get_input_message_reply_to(), rand_bool(), rand_bool(),
std::move(input_message_contents)));
} else {
send_request(td_api::make_object<td_api::sendMessageAlbum>(
chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(),
std::move(input_message_contents)));
}
} else if (op == "smad") { } else if (op == "smad") {
ChatId chat_id; ChatId chat_id;
get_args(args, chat_id, args); get_args(args, chat_id, args);
@ -4784,8 +4790,15 @@ class CliClient final : public Actor {
td_api::make_object<td_api::inputMessageDocument>(as_input_file(document), nullptr, true, as_caption("")); td_api::make_object<td_api::inputMessageDocument>(as_input_file(document), nullptr, true, as_caption(""));
return content; return content;
}); });
send_request(td_api::make_object<td_api::sendMessageAlbum>( if (!business_connection_id_.empty()) {
chat_id, message_thread_id_, nullptr, default_message_send_options(), std::move(input_message_contents))); send_request(td_api::make_object<td_api::sendBusinessMessageAlbum>(
business_connection_id_, chat_id, get_input_message_reply_to(), rand_bool(), rand_bool(),
std::move(input_message_contents)));
} else {
send_request(td_api::make_object<td_api::sendMessageAlbum>(
chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(),
std::move(input_message_contents)));
}
} else if (op == "gmft") { } else if (op == "gmft") {
auto r_message_file_head = read_file_str(args, 2 << 10); auto r_message_file_head = read_file_str(args, 2 << 10);
if (r_message_file_head.is_error()) { if (r_message_file_head.is_error()) {