diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index ac1659f72..89086fa3b 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -4587,6 +4587,12 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok; +//@description Changes chosen reaction for a message +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +//@reaction Text representation of the chosen reaction, or an empty string to remove reaction +setMessageReaction chat_id:int53 message_id:int53 reaction:string = Ok; + //@description Returns reactions chosen for a message, along with their source //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 93b488947..fee067b9b 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -5649,6 +5649,10 @@ bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogActi return !td->stickers_manager_->is_sent_animated_emoji_click(dialog_id, remove_emoji_modifiers(emoji)); } +bool is_active_reaction(Td *td, const string &reaction) { + return td->stickers_manager_->is_active_reaction(reaction); +} + void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) { send_closure(G()->top_dialog_manager(), &TopDialogManager::on_dialog_used, category, dialog_id, date); } diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index b74177e9d..31ce83f38 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -248,6 +248,8 @@ void on_sent_message_content(Td *td, const MessageContent *content); bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action); +bool is_active_reaction(Td *td, const string &reaction); + void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date); void update_used_hashtags(Td *td, const MessageContent *content); diff --git a/td/telegram/MessageReaction.cpp b/td/telegram/MessageReaction.cpp index 173951d15..2415d2f6f 100644 --- a/td/telegram/MessageReaction.cpp +++ b/td/telegram/MessageReaction.cpp @@ -9,6 +9,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UpdatesManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -18,6 +19,50 @@ namespace td { +class SendReactionQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + MessageId message_id_; + + public: + explicit SendReactionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(FullMessageId full_message_id, string reaction) { + dialog_id_ = full_message_id.get_dialog_id(); + message_id_ = full_message_id.get_message_id(); + + 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")); + } + + int32 flags = 0; + if (!reaction.empty()) { + flags |= telegram_api::messages_sendReaction::REACTION_MASK; + } + + send_query(G()->net_query_creator().create(telegram_api::messages_sendReaction( + flags, std::move(input_peer), message_id_.get_server_message_id().get(), reaction))); + } + + 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 SendReactionQuery: " << 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, "SendReactionQuery"); + promise_.set_error(std::move(status)); + } +}; + class GetMessageReactionsListQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; @@ -95,6 +140,19 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { } }; +void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id) { + if (is_chosen_ == is_chosen) { + return; + } + + is_chosen_ = is_chosen; + + if (chooser_dialog_id.is_valid()) { + choose_count_ += is_chosen_ ? 1 : -1; + // TODO update recent_chooser_dialog_ids_, but only if not broadcast + } +} + td_api::object_ptr MessageReaction::get_message_reaction_object(Td *td) const { CHECK(!is_empty()); @@ -250,13 +308,17 @@ bool MessageReactions::need_update_message_reactions(const MessageReactions *old return true; } - // has_pending_reaction_ and old_chosen_reaction_ don't affect visible state + // has_pending_reaction_ doesn't affect visible state // compare all other fields return old_reactions->reactions_ != new_reactions->reactions_ || old_reactions->is_min_ != new_reactions->is_min_ || old_reactions->can_see_all_choosers_ != new_reactions->can_see_all_choosers_ || old_reactions->need_polling_ != new_reactions->need_polling_; } +void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, Promise &&promise) { + td->create_handler(std::move(promise))->send(full_message_id, std::move(reaction)); +} + void get_message_chosen_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, Promise> &&promise) { if (!td->messages_manager_->have_message_force(full_message_id, "get_message_chosen_reactions")) { @@ -264,7 +326,8 @@ void get_message_chosen_reactions(Td *td, FullMessageId full_message_id, string } auto message_id = full_message_id.get_message_id(); - if (!message_id.is_valid() || !message_id.is_server()) { + if (full_message_id.get_dialog_id().get_type() == DialogType::SecretChat || !message_id.is_valid() || + !message_id.is_server()) { return promise.set_value(td_api::make_object(0, Auto(), string())); } diff --git a/td/telegram/MessageReaction.h b/td/telegram/MessageReaction.h index 0282baa7c..353183f5d 100644 --- a/td/telegram/MessageReaction.h +++ b/td/telegram/MessageReaction.h @@ -41,7 +41,7 @@ class MessageReaction { MessageReaction() = default; - MessageReaction(string &&reaction, int32 choose_count, bool is_chosen, vector &&recent_chooser_dialog_ids, + MessageReaction(string reaction, int32 choose_count, bool is_chosen, vector &&recent_chooser_dialog_ids, vector> &&recent_chooser_min_channels) : reaction_(std::move(reaction)) , choose_count_(choose_count) @@ -62,9 +62,7 @@ class MessageReaction { return is_chosen_; } - void set_is_chosen(bool is_chosen) { - is_chosen_ = is_chosen; - } + void set_is_chosen(bool is_chosen, DialogId chooser_dialog_id = DialogId()); const vector &get_recent_chooser_dialog_ids() const { return recent_chooser_dialog_ids_; @@ -97,7 +95,6 @@ struct MessageReactions { bool need_polling_ = true; bool can_see_all_choosers_ = false; bool has_pending_reaction_ = false; - string old_chosen_reaction_; MessageReactions() = default; @@ -117,6 +114,8 @@ struct MessageReactions { void parse(ParserT &parser); }; +void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, Promise &&promise); + void get_message_chosen_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, Promise> &&promise); diff --git a/td/telegram/MessageReaction.hpp b/td/telegram/MessageReaction.hpp index d6d06a4ec..807eafa96 100644 --- a/td/telegram/MessageReaction.hpp +++ b/td/telegram/MessageReaction.hpp @@ -56,41 +56,31 @@ void MessageReaction::parse(ParserT &parser) { template void MessageReactions::store(StorerT &storer) const { bool has_reactions = !reactions_.empty(); - bool has_old_chosen_reaction = !old_chosen_reaction_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_min_); STORE_FLAG(need_polling_); STORE_FLAG(can_see_all_choosers_); STORE_FLAG(has_pending_reaction_); STORE_FLAG(has_reactions); - STORE_FLAG(has_old_chosen_reaction); END_STORE_FLAGS(); if (has_reactions) { td::store(reactions_, storer); } - if (has_old_chosen_reaction) { - td::store(old_chosen_reaction_, storer); - } } template void MessageReactions::parse(ParserT &parser) { bool has_reactions; - bool has_old_chosen_reaction; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_min_); PARSE_FLAG(need_polling_); PARSE_FLAG(can_see_all_choosers_); PARSE_FLAG(has_pending_reaction_); PARSE_FLAG(has_reactions); - PARSE_FLAG(has_old_chosen_reaction); END_PARSE_FLAGS(); if (has_reactions) { td::parse(reactions_, parser); } - if (has_old_chosen_reaction) { - td::parse(old_chosen_reaction_, parser); - } } } // namespace td diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index f03694521..a7ab06ba5 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -23720,6 +23720,72 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id } } +void MessagesManager::set_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise) { + auto dialog_id = full_message_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id, "set_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(), "set_message_reaction"); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + + if (full_message_id.get_dialog_id().get_type() == DialogType::SecretChat || !m->message_id.is_valid() || + !m->message_id.is_server()) { + return promise.set_error(Status::Error(400, "Message can't have reactions")); + } + + if (!reaction.empty() && !is_active_reaction(td_, reaction)) { + return promise.set_error(Status::Error(400, "Invalid reaction specified")); + } + + if (m->reactions == nullptr) { + if (reaction.empty()) { + return promise.set_value(Unit()); + } + + m->reactions = make_unique(); + } + + bool is_found = false; + for (auto it = m->reactions->reactions_.begin(); it != m->reactions->reactions_.end();) { + auto &message_reaction = *it; + if (message_reaction.is_chosen()) { + if (message_reaction.get_reaction() == reaction) { + return promise.set_value(Unit()); + } + message_reaction.set_is_chosen(false); + if (message_reaction.is_empty()) { + it = m->reactions->reactions_.erase(it); + continue; + } + } else { + if (message_reaction.get_reaction() == reaction) { + message_reaction.set_is_chosen(true); + is_found = true; + } + } + + ++it; + } + // m->reactions->has_pending_reaction_ = true; + if (!is_found && !reaction.empty()) { + // TODO place to the correct position + vector recent_chooser_dialog_ids; + if (!is_broadcast_channel(dialog_id)) { + recent_chooser_dialog_ids.push_back(get_my_dialog_id()); + } + m->reactions->reactions_.emplace_back(reaction, 1, true, std::move(recent_chooser_dialog_ids), Auto()); + } + + send_update_message_interaction_info(dialog_id, m); + on_message_changed(d, m, true, "set_message_reaction"); + + ::td::set_message_reaction(td_, full_message_id, std::move(reaction), std::move(promise)); +} + void MessagesManager::get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit, Promise> &&promise) { auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, offset = std::move(offset), diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index ba7991372..c10a3a859 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -780,6 +780,8 @@ class MessagesManager final : public Actor { vector get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, Promise &&promise); + void set_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise); + void get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit, Promise> &&promise); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index b893fd4ce..e44359170 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5244,10 +5244,18 @@ void Td::on_request(uint64 id, const td_api::getChatScheduledMessages &request) CREATE_REQUEST(GetChatScheduledMessagesRequest, request.chat_id_); } +void Td::on_request(uint64 id, td_api::setMessageReaction &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.reaction_); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->set_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(request.reaction_), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getMessageChosenReactions &request) { CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.offset_); CLEAN_INPUT_STRING(request.reaction_); + CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); get_message_chosen_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(request.reaction_), std::move(request.offset_), request.limit_, diff --git a/td/telegram/Td.h b/td/telegram/Td.h index f624ab2d5..99d24ec8e 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -645,6 +645,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatScheduledMessages &request); + void on_request(uint64 id, td_api::setMessageReaction &request); + void on_request(uint64 id, td_api::getMessageChosenReactions &request); void on_request(uint64 id, td_api::getMessagePublicForwards &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 1915b9d98..75c89bcac 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2058,6 +2058,12 @@ class CliClient final : public Actor { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); + } else if (op == "react") { + ChatId chat_id; + MessageId message_id; + string reaction; + get_args(args, chat_id, message_id, reaction); + send_request(td_api::make_object(chat_id, message_id, reaction)); } else if (op == "gmcr") { ChatId chat_id; MessageId message_id;