From b852bd145f37aa4b05a3f153239e90a0aef0252c Mon Sep 17 00:00:00 2001 From: levlam Date: Thu, 21 Feb 2019 15:23:05 +0300 Subject: [PATCH] Add td_api::setPollAnswer. GitOrigin-RevId: 40c409ea8b4bba6d1ee0137a5cab8dca889d71dd --- td/generate/TlDocumentationGenerator.php | 2 +- td/generate/scheme/td_api.tl | 5 + td/generate/scheme/td_api.tlo | Bin 145536 -> 145716 bytes td/telegram/MessageContent.cpp | 7 + td/telegram/MessageContent.h | 3 + td/telegram/MessagesManager.cpp | 32 ++++- td/telegram/MessagesManager.h | 2 + td/telegram/PollManager.cpp | 173 ++++++++++++++++++++++- td/telegram/PollManager.h | 20 ++- td/telegram/Td.cpp | 9 +- td/telegram/Td.h | 2 +- td/telegram/cli.cpp | 9 ++ 12 files changed, 249 insertions(+), 15 deletions(-) diff --git a/td/generate/TlDocumentationGenerator.php b/td/generate/TlDocumentationGenerator.php index 8868ad2f2..fd4c7a758 100644 --- a/td/generate/TlDocumentationGenerator.php +++ b/td/generate/TlDocumentationGenerator.php @@ -213,7 +213,7 @@ abstract class TlDocumentationGenerator foreach ($info as $name => $value) { if (!$value) { $this->printError("info[$name] for $class_name is empty"); - } elseif ($value[0] < 'A' || $value[0] > 'Z') { + } elseif (($value[0] < 'A' || $value[0] > 'Z') && ($value[0] < '0' || $value[0] > '9')) { $this->printError("info[$name] for $class_name doesn't begins with capital letter"); } } diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 7416429af..4cce9efbb 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -2906,6 +2906,11 @@ getJsonValue json:string = JsonValue; getJsonString json_value:JsonValue = Text; +//@description Changes user answer to a poll @chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll +//@option_ids 0-based identifiers of options, chosen by the user. Currently user can't choose more than 1 option +setPollAnswer chat_id:int53 message_id:int53 option_ids:vector = Ok; + + //@description Sends an inline query to a bot and returns its results. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires @bot_user_id The identifier of the target bot //@chat_id Identifier of the chat, where the query was sent @user_location Location of the user, only if needed @query Text of the query @offset Offset of the first entry to return getInlineQueryResults bot_user_id:int32 chat_id:int53 user_location:location query:string offset:string = InlineQueryResults; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 759b2e670629ec48bb103e042c571f4a56acc640..2cc0275cb14c42092431e929ed402155d97ed1a7 100644 GIT binary patch delta 69 zcmV-L0J{Hx@d&i?2!ON!gqQ(Gw}qGiD~=F|1(&Q1b7gc;Z)|KqZgY2+fSCa_mq1(r bBnt{}aCB*JZeM9+bC+M50YSI0CIZkA$1@s) delta 25 hcmdn;iKF2oN5dAzmKlt0+goQaYWHnVQD?d!2mqCa3UdGe diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 9f76ba495..0c4f67d28 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -2679,6 +2679,13 @@ void set_message_content_web_page_id(MessageContent *content, WebPageId web_page static_cast(content)->web_page_id = web_page_id; } +void set_message_content_poll_answer(Td *td, MessageContent *content, FullMessageId full_message_id, + vector &&option_ids, Promise &&promise) { + CHECK(content->get_type() == MessageContentType::Poll); + td->poll_manager_->set_poll_answer(static_cast(content)->poll_id, full_message_id, + std::move(option_ids), std::move(promise)); +} + static void merge_location_access_hash(const Location &first, const Location &second) { if (second.get_access_hash() != 0) { first.set_access_hash(second.get_access_hash()); diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index 931ba82c2..7a54817b6 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -179,6 +179,9 @@ WebPageId get_message_content_web_page_id(const MessageContent *content); void set_message_content_web_page_id(MessageContent *content, WebPageId web_page_id); +void set_message_content_poll_answer(Td *td, MessageContent *content, FullMessageId full_message_id, + vector &&option_ids, Promise &&promise); + void merge_message_contents(Td *td, const MessageContent *old_content, MessageContent *new_content, bool need_message_changed_warning, DialogId dialog_id, bool need_merge_files, bool &is_content_changed, bool &need_update); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index ea2eaaff9..6abeb4be1 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -24874,12 +24874,32 @@ void MessagesManager::suffix_load_till_message_id(Dialog *d, MessageId message_i suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition))); } +void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector &&option_ids, + Promise &&promise) { + auto m = get_message_force(full_message_id); + if (m == nullptr) { + return promise.set_error(Status::Error(5, "Message not found")); + } + if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) { + return promise.set_error(Status::Error(3, "Can't access the chat")); + } + if (m->content->get_type() != MessageContentType::Poll) { + return promise.set_error(Status::Error(5, "Message is not a poll")); + } + auto message_id = full_message_id.get_message_id(); + if (!message_id.is_server()) { + return promise.set_error(Status::Error(5, "Poll can't be answered")); + } + + set_message_content_poll_answer(td_, m->content.get(), full_message_id, std::move(option_ids), std::move(promise)); +} + Result MessagesManager::get_invoice_message_id(FullMessageId full_message_id) { - auto message = get_message_force(full_message_id); - if (message == nullptr) { + auto m = get_message_force(full_message_id); + if (m == nullptr) { return Status::Error(5, "Message not found"); } - if (message->content->get_type() != MessageContentType::Invoice) { + if (m->content->get_type() != MessageContentType::Invoice) { return Status::Error(5, "Message has no invoice"); } auto message_id = full_message_id.get_message_id(); @@ -24926,11 +24946,11 @@ void MessagesManager::send_payment_form(FullMessageId full_message_id, const str void MessagesManager::get_payment_receipt(FullMessageId full_message_id, Promise> &&promise) { - auto message = get_message_force(full_message_id); - if (message == nullptr) { + auto m = get_message_force(full_message_id); + if (m == nullptr) { return promise.set_error(Status::Error(5, "Message not found")); } - if (message->content->get_type() != MessageContentType::PaymentSuccessful) { + if (m->content->get_type() != MessageContentType::PaymentSuccessful) { return promise.set_error(Status::Error(5, "Message has wrong type")); } auto message_id = full_message_id.get_message_id(); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 8982992f1..ec0d1e447 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -702,6 +702,8 @@ class MessagesManager : public Actor { void on_binlog_events(vector &&events); + void set_poll_answer(FullMessageId full_message_id, vector &&option_ids, Promise &&promise); + void get_payment_form(FullMessageId full_message_id, Promise> &&promise); void validate_order_info(FullMessageId full_message_id, tl_object_ptr order_info, bool allow_save, diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index e112952c7..01a0691ee 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -9,9 +9,12 @@ #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/net/NetActor.h" #include "td/telegram/PollManager.hpp" +#include "td/telegram/SequenceDispatcher.h" #include "td/telegram/TdDb.h" #include "td/telegram/Td.h" +#include "td/telegram/UpdatesManager.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" @@ -20,8 +23,53 @@ #include "td/utils/misc.h" #include "td/utils/Status.h" +#include + namespace td { +class SetPollAnswerQuery : public NetActorOnce { + Promise promise_; + DialogId dialog_id_; + + public: + explicit SetPollAnswerQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(FullMessageId full_message_id, vector &&options, uint64 generation) { + dialog_id_ = full_message_id.get_dialog_id(); + auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + if (input_peer == nullptr) { + LOG(INFO) << "Can't set poll answer, because have no read access to " << dialog_id_; + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + auto message_id = full_message_id.get_message_id().get_server_message_id().get(); + auto query = G()->net_query_creator().create( + create_storer(telegram_api::messages_sendVote(std::move(input_peer), message_id, std::move(options)))); + auto sequence_id = -1; + send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback, + std::move(query), actor_shared(this), sequence_id); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive sendVote result: " << to_string(result); + + td->updates_manager_->on_get_updates(std::move(result)); + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetPollAnswerQuery"); + promise_.set_error(std::move(status)); + } +}; + PollManager::PollManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } @@ -130,8 +178,28 @@ td_api::object_ptr PollManager::get_poll_option_object(const td_api::object_ptr PollManager::get_poll_object(PollId poll_id) const { auto poll = get_poll(poll_id); CHECK(poll != nullptr); - return td_api::make_object(poll->question, transform(poll->options, get_poll_option_object), - poll->total_voter_count, poll->is_closed); + vector> poll_options; + auto it = pending_answers_.find(poll_id); + int32 voter_count_diff = 0; + if (it == pending_answers_.end()) { + poll_options = transform(poll->options, get_poll_option_object); + } else { + auto &chosen_options = it->second.options_; + for (auto &poll_option : poll->options) { + auto is_chosen = + std::find(chosen_options.begin(), chosen_options.end(), poll_option.data) != chosen_options.end(); + if (poll_option.is_chosen) { + voter_count_diff = -1; + } + poll_options.push_back( + td_api::make_object(poll_option.text, poll_option.voter_count - static_cast(poll_option.is_chosen) + static_cast(is_chosen), is_chosen)); + } + if (!chosen_options.empty()) { + voter_count_diff++; + } + } + return td_api::make_object(poll->question, std::move(poll_options), poll->total_voter_count + voter_count_diff, + poll->is_closed); } telegram_api::object_ptr PollManager::get_input_poll_option(const PollOption &poll_option) { @@ -166,6 +234,107 @@ void PollManager::unregister_poll(PollId poll_id, FullMessageId full_message_id) poll_messages_[poll_id].erase(full_message_id); } +void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector &&option_ids, + Promise &&promise) { + if (option_ids.size() > 1) { + return promise.set_error(Status::Error(400, "Can't choose more than 1 option")); + } + if (is_local_poll_id(poll_id)) { + return promise.set_error(Status::Error(5, "Poll can't be answered")); + } + + auto poll = get_poll(poll_id); + CHECK(poll != nullptr); + if (poll->is_closed) { + return promise.set_error(Status::Error(400, "Can't answer closed poll")); + } + vector options; + for (auto &option_id : option_ids) { + auto index = static_cast(option_id); + if (index >= poll->options.size()) { + return promise.set_error(Status::Error(400, "Invalid option id specified")); + } + options.push_back(poll->options[index].data); + } + + do_set_poll_answer(poll_id, full_message_id, std::move(options), 0, std::move(promise)); +} + +void PollManager::do_set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector &&options, + uint64 logevent_id, Promise &&promise) { + auto &pending_answer = pending_answers_[poll_id]; + if (!pending_answer.promises_.empty() && pending_answer.options_ == options) { + pending_answer.promises_.push_back(std::move(promise)); + return; + } + + if (logevent_id == 0 && G()->parameters().use_message_db) { + // TODO add logevent or rewrite pending_answer.logevent_id_ + } + + if (!pending_answer.promises_.empty()) { + auto promises = std::move(pending_answer.promises_); + pending_answer.promises_.clear(); + for (auto &old_promise : promises) { + old_promise.set_value(Unit()); + } + } + + vector sent_options; + for (auto &option : options) { + sent_options.emplace_back(option); + } + + auto generation = ++current_generation_; + + pending_answer.options_ = std::move(options); + pending_answer.promises_.push_back(std::move(promise)); + pending_answer.generation_ = generation; + pending_answer.logevent_id_ = logevent_id; + + notify_on_poll_update(poll_id); + + auto query_promise = PromiseCreator::lambda([poll_id, generation, actor_id = actor_id(this)](Result &&result) { + send_closure(actor_id, &PollManager::on_set_poll_answer, poll_id, generation, std::move(result)); + }); + + send_closure(td_->create_net_actor(std::move(query_promise)), &SetPollAnswerQuery::send, + full_message_id, std::move(sent_options), generation); +} + +void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation, Result &&result) { + if (G()->close_flag() && result.is_error()) { + // request will be resent after restart + return; + } + auto it = pending_answers_.find(poll_id); + if (it == pending_answers_.end()) { + // can happen if this is an answer with mismatched generation and server has ignored invoke-after + return; + } + + auto &pending_answer = it->second; + CHECK(!pending_answer.promises_.empty()); + if (pending_answer.generation_ != generation) { + return; + } + + if (pending_answer.logevent_id_ != 0) { + // TODO delete logevent + } + + auto promises = std::move(pending_answer.promises_); + for (auto &promise : promises) { + if (result.is_ok()) { + promise.set_value(Unit()); + } else { + promise.set_error(result.error().clone()); + } + } + + pending_answers_.erase(it); +} + void PollManager::close_poll(PollId poll_id) { auto poll = get_poll_editable(poll_id); CHECK(poll != nullptr); diff --git a/td/telegram/PollManager.h b/td/telegram/PollManager.h index c682784d4..135b3cba6 100644 --- a/td/telegram/PollManager.h +++ b/td/telegram/PollManager.h @@ -39,6 +39,9 @@ class PollManager : public Actor { void unregister_poll(PollId poll_id, FullMessageId full_message_id); + void set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector &&option_ids, + Promise &&promise); + void close_poll(PollId poll_id); tl_object_ptr get_input_media(PollId poll_id) const; @@ -83,7 +86,7 @@ class PollManager : public Actor { static bool is_local_poll_id(PollId poll_id); - static td_api::object_ptr get_poll_option_object(const PollOption &poll_option); + static td_api::object_ptr PollManager::get_poll_option_object(const PollOption &poll_option); static telegram_api::object_ptr get_input_poll_option(const PollOption &poll_option); @@ -107,14 +110,29 @@ class PollManager : public Actor { Poll *get_poll_force(PollId poll_id); + void do_set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector &&options, uint64 logevent_id, + Promise &&promise); + + void on_set_poll_answer(PollId poll_id, uint64 generation, Result &&result); + Td *td_; ActorShared<> parent_; std::unordered_map, PollIdHash> polls_; std::unordered_map, PollIdHash> poll_messages_; + struct PendingPollAnswer { + vector options_; + vector> promises_; + uint64 generation_ = 0; + uint64 logevent_id_ = 0; + }; + std::unordered_map pending_answers_; + int64 current_local_poll_id_ = 0; + uint64 current_generation_ = 0; + std::unordered_set loaded_from_database_polls_; }; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index eaf25fe9d..52efb3a15 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6540,13 +6540,14 @@ void Td::on_request(uint64 id, td_api::setOption &request) { return send_error_raw(id, 3, "Option can't be set"); } -/* -void Td::on_request(uint64 id, td_api::setPollAnswers &request) { + +void Td::on_request(uint64 id, td_api::setPollAnswer &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - messages_manager_->set_poll_answers({DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(request.option_ids_)); + messages_manager_->set_poll_answer({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(request.option_ids_), std::move(promise)); } -*/ + void Td::on_request(uint64 id, td_api::getInlineQueryResults &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index dda50cd11..9e1359585 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -830,7 +830,7 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::setOption &request); - // void on_request(uint64 id, td_api::setPollAnswers &request); + void on_request(uint64 id, td_api::setPollAnswer &request); void on_request(uint64 id, td_api::getInlineQueryResults &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 9f494b9ba..78918fb75 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -3212,6 +3212,15 @@ class CliClient final : public Actor { std::tie(chat_id, user_ids) = split(args); send_request(make_tl_object(as_chat_id(chat_id), as_user_ids(user_ids, ','))); + } else if (op == "spolla") { + string chat_id; + string message_id; + string option_ids; + + std::tie(chat_id, args) = split(args); + std::tie(message_id, option_ids) = split(args); + send_request(make_tl_object(as_chat_id(chat_id), as_message_id(message_id), + to_integers(option_ids))); } else { op_not_found_count++; }