Add td_api::getPollVoters.

GitOrigin-RevId: 47ad78287433a2efb9b66f18a960e4de6060842a
This commit is contained in:
levlam 2020-01-13 21:21:58 +03:00
parent f2211527db
commit 4e22f29c53
12 changed files with 341 additions and 30 deletions

View File

@ -3427,10 +3427,16 @@ getJsonValue json:string = JsonValue;
getJsonString json_value:JsonValue = Text;
//@description Changes the user answer to a poll. A quiz poll can be answered only once @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. User can choose more than 1 option only is the poll allows multiple answers
//@description Changes the user answer to a poll. A quiz poll can be answered only once
//@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 answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers
setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@description Returns users voted for the specified option in a non-anonymous polls. For the optimal performance the number of returned users is chosen by the library
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_id 0-based identifier of the answer option @offset Number of users to skip in the result; must be non-negative
getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 = Users;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll @reply_markup The new message reply markup; for bots only
stopPoll chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Ok;

Binary file not shown.

View File

@ -429,7 +429,7 @@ class MessageChatSetTtl : public MessageContent {
class MessageUnsupported : public MessageContent {
public:
static constexpr int32 CURRENT_VERSION = 4;
static constexpr int32 CURRENT_VERSION = 5;
int32 version = CURRENT_VERSION;
MessageUnsupported() = default;
@ -2774,18 +2774,26 @@ void set_message_content_web_page_id(MessageContent *content, WebPageId web_page
static_cast<MessageText *>(content)->web_page_id = web_page_id;
}
void set_message_content_poll_answer(Td *td, MessageContent *content, FullMessageId full_message_id,
void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
vector<int32> &&option_ids, Promise<Unit> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->set_poll_answer(static_cast<MessagePoll *>(content)->poll_id, full_message_id,
td->poll_manager_->set_poll_answer(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
std::move(option_ids), std::move(promise));
}
void stop_message_content_poll(Td *td, MessageContent *content, FullMessageId full_message_id,
void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->get_poll_voters(static_cast<const MessagePoll *>(content)->poll_id, full_message_id, option_id,
offset, std::move(promise));
}
void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->stop_poll(static_cast<MessagePoll *>(content)->poll_id, full_message_id, std::move(reply_markup),
std::move(promise));
td->poll_manager_->stop_poll(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
std::move(reply_markup), std::move(promise));
}
static void merge_location_access_hash(const Location &first, const Location &second) {

View File

@ -190,10 +190,14 @@ 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,
void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
vector<int32> &&option_ids, Promise<Unit> &&promise);
void stop_message_content_poll(Td *td, MessageContent *content, FullMessageId full_message_id,
void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise);
void merge_message_contents(Td *td, const MessageContent *old_content, MessageContent *new_content,

View File

@ -2595,7 +2595,7 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
TRY_RESULT(result, clean_input_string_with_entities(text, entities));
// now entities are still sorted by offset and length, but not type,
// because some characters could be deleted and some entities bacame to end together
// because some characters could be deleted and after that some entities begin to share a common end
size_t last_non_whitespace_pos;
int32 last_non_whitespace_utf16_offset;

View File

@ -30118,6 +30118,28 @@ void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector<int3
set_message_content_poll_answer(td_, m->content.get(), full_message_id, std::move(option_ids), std::move(promise));
}
void MessagesManager::get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
auto m = get_message_force(full_message_id, "get_poll_voters");
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"));
}
if (m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(5, "Can't get poll results from scheduled messages"));
}
if (!m->message_id.is_server()) {
return promise.set_error(Status::Error(5, "Poll results can't be received"));
}
get_message_content_poll_voters(td_, m->content.get(), full_message_id, option_id, offset, std::move(promise));
}
void MessagesManager::stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "stop_poll");

View File

@ -803,6 +803,9 @@ class MessagesManager : public Actor {
void set_poll_answer(FullMessageId full_message_id, vector<int32> &&option_ids, Promise<Unit> &&promise);
void get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise);

View File

@ -40,6 +40,7 @@
#include <algorithm>
#include <limits>
#include <unordered_map>
namespace td {
@ -84,6 +85,54 @@ class GetPollResultsQuery : public Td::ResultHandler {
}
};
class GetPollVotersQuery : public Td::ResultHandler {
Promise<tl_object_ptr<telegram_api::messages_votesList>> promise_;
PollId poll_id_;
DialogId dialog_id_;
public:
explicit GetPollVotersQuery(Promise<tl_object_ptr<telegram_api::messages_votesList>> &&promise)
: promise_(std::move(promise)) {
}
void send(PollId poll_id, FullMessageId full_message_id, BufferSlice &&option, const string &offset, int32 limit) {
poll_id_ = poll_id;
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 get poll, because have no read access to " << dialog_id_;
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
CHECK(!option.empty());
int32 flags = telegram_api::messages_getPollVotes::OPTION_MASK;
if (!offset.empty()) {
flags |= telegram_api::messages_getPollVotes::OFFSET_MASK;
}
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getPollVotes(
flags, std::move(input_peer), message_id, std::move(option), offset, limit))));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::messages_getPollVotes>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
void on_error(uint64 id, Status status) override {
if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPollVotersQuery") &&
status.message() != "MESSAGE_ID_INVALID") {
LOG(ERROR) << "Receive " << status << ", while trying to get voters of " << poll_id_;
}
promise_.set_error(std::move(status));
}
};
class SetPollAnswerActor : public NetActorOnce {
Promise<tl_object_ptr<telegram_api::Updates>> promise_;
DialogId dialog_id_;
@ -594,6 +643,7 @@ void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id,
return promise.set_error(Status::Error(400, "Can't retract vote in a quiz"));
}
std::unordered_map<size_t, int> affected_option_ids;
vector<string> options;
for (auto &option_id : option_ids) {
auto index = static_cast<size_t>(option_id);
@ -601,6 +651,18 @@ void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id,
return promise.set_error(Status::Error(400, "Invalid option ID specified"));
}
options.push_back(poll->options[index].data);
affected_option_ids[index]++;
}
for (size_t option_index = 0; option_index < poll->options.size(); option_index++) {
if (poll->options[option_index].is_chosen) {
affected_option_ids[option_index]++;
}
}
for (auto it : affected_option_ids) {
if (it.second == 1) {
invalidate_poll_option_voters(poll, poll_id, it.first);
}
}
do_set_poll_answer(poll_id, full_message_id, std::move(options), 0, std::move(promise));
@ -733,6 +795,157 @@ void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation,
}
}
void PollManager::invalidate_poll_voters(const Poll *poll, PollId poll_id) {
if (poll->is_anonymous) {
return;
}
auto it = poll_voters_.find(poll_id);
if (it == poll_voters_.end()) {
return;
}
for (auto &voters : it->second) {
voters.was_invalidated = true;
}
}
void PollManager::invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index) {
if (poll->is_anonymous) {
return;
}
auto it = poll_voters_.find(poll_id);
if (it == poll_voters_.end()) {
return;
}
auto &poll_voters = it->second;
CHECK(poll_voters.size() == poll->options.size());
CHECK(option_index < poll_voters.size());
poll_voters[option_index].was_invalidated = true;
}
PollManager::PollOptionVoters &PollManager::get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id) {
auto &poll_voters = poll_voters_[poll_id];
if (poll_voters.empty()) {
poll_voters.resize(poll->options.size());
}
auto index = narrow_cast<size_t>(option_id);
CHECK(index < poll_voters.size());
return poll_voters[index];
}
void PollManager::get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
if (is_local_poll_id(poll_id)) {
return promise.set_error(Status::Error(400, "Poll results can't be received"));
}
if (offset < 0) {
return promise.set_error(Status::Error(400, "Invalid offset specified"));
}
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
return promise.set_error(Status::Error(400, "Invalid option ID specified"));
}
if (poll->is_anonymous) {
return promise.set_error(Status::Error(400, "Poll is anonymous"));
}
auto &voters = get_poll_option_voters(poll, poll_id, option_id);
if (voters.pending_queries.empty() && voters.was_invalidated && offset == 0) {
voters.voter_user_ids.clear();
voters.next_offset.clear();
voters.was_invalidated = false;
}
auto cur_offset = narrow_cast<int32>(voters.voter_user_ids.size());
if (offset > cur_offset) {
return promise.set_error(Status::Error(400, "Too big offset specified, voters can be received only consequently"));
}
if (offset < cur_offset) {
vector<UserId> result;
for (int32 i = offset; i != cur_offset && i - offset < MAX_GET_POLL_VOTERS; i++) {
result.push_back(voters.voter_user_ids[i]);
}
return promise.set_value({poll->options[option_id].voter_count, std::move(result)});
}
if (poll->options[option_id].voter_count == 0) {
return promise.set_value({0, vector<UserId>()});
}
voters.pending_queries.push_back(std::move(promise));
if (voters.pending_queries.size() > 1) {
return;
}
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), poll_id, option_id](
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) {
send_closure(actor_id, &PollManager::on_get_poll_voters, poll_id, option_id, std::move(result));
});
td_->create_handler<GetPollVotersQuery>(std::move(query_promise))
->send(poll_id, full_message_id, BufferSlice(poll->options[option_id].data), voters.next_offset,
MAX_GET_POLL_VOTERS);
}
void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id,
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
LOG(ERROR) << "Can't process voters for option " << option_id << " in " << poll_id << ", because it has only "
<< poll->options.size() << " options";
return;
}
if (poll->is_anonymous) {
// just in case
result = Status::Error(400, "Poll is anonymous");
}
auto &voters = get_poll_option_voters(poll, poll_id, option_id);
auto promises = std::move(voters.pending_queries);
CHECK(!promises.empty());
if (result.is_error()) {
for (auto &promise : promises) {
promise.set_error(result.error().clone());
}
return;
}
auto vote_list = result.move_as_ok();
td_->contacts_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters");
voters.next_offset = std::move(vote_list->next_offset_);
if (poll->options[option_id].voter_count != vote_list->count_) {
++current_generation_;
update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
}
vector<UserId> user_ids;
for (auto &voter : vote_list->votes_) {
UserId user_id(voter->user_id_);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive " << user_id << " as voter in " << poll_id;
continue;
}
if (voter->option_ != poll->options[option_id].data) {
LOG(ERROR) << "Receive " << user_id << " in " << poll_id << " voted for unexpected option";
continue;
}
voters.voter_user_ids.push_back(user_id);
user_ids.push_back(user_id);
}
for (auto &promise : promises) {
promise.set_value({vote_list->count_, vector<UserId>(user_ids)});
}
}
void PollManager::stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
if (is_local_poll_id(poll_id)) {
@ -1010,7 +1223,8 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
for (size_t i = 0; i < poll_results->results_.size(); i++) {
auto &poll_result = poll_results->results_[i];
Slice data = poll_result->option_.as_slice();
for (auto &option : poll->options) {
for (size_t option_index = 0; option_index < poll->options.size(); i++) {
auto &option = poll->options[option_index];
if (option.data != data) {
continue;
}
@ -1028,26 +1242,28 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
}
correct_option_id = static_cast<int32>(i);
}
if (poll_result->voters_ < 0) {
LOG(ERROR) << "Receive " << poll_result->voters_ << " voters for an option in " << poll_id;
poll_result->voters_ = 0;
}
if (option.is_chosen && poll_result->voters_ == 0) {
LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id;
poll_result->voters_ = 1;
}
if (poll_result->voters_ > poll->total_voter_count) {
LOG(ERROR) << "Have only " << poll->total_voter_count << " poll voters, but there are " << poll_result->voters_
<< " voters for an option in " << poll_id;
poll->total_voter_count = poll_result->voters_;
}
auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
if (poll_result->voters_ > max_voter_count) {
LOG(ERROR) << "Have too much " << poll_result->voters_ << " poll voters for an option in " << poll_id;
poll_result->voters_ = max_voter_count;
}
if (poll_result->voters_ != option.voter_count) {
invalidate_poll_option_voters(poll, poll_id, option_index);
option.voter_count = poll_result->voters_;
if (option.voter_count < 0) {
LOG(ERROR) << "Receive " << option.voter_count << " voters for an option in " << poll_id;
option.voter_count = 0;
}
if (option.is_chosen && option.voter_count == 0) {
LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id;
option.voter_count = 1;
}
if (option.voter_count > poll->total_voter_count) {
LOG(ERROR) << "Have only " << poll->total_voter_count << " poll voters, but there are " << option.voter_count
<< " voters for an option in " << poll_id;
poll->total_voter_count = option.voter_count;
}
auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
if (option.voter_count > max_voter_count) {
LOG(ERROR) << "Have too much " << option.voter_count << " poll voters for an option in " << poll_id;
option.voter_count = max_voter_count;
}
is_changed = true;
}
}
@ -1090,6 +1306,7 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
}
if (recent_voter_user_ids != poll->recent_voter_user_ids) {
poll->recent_voter_user_ids = std::move(recent_voter_user_ids);
invalidate_poll_voters(poll, poll_id);
is_changed = true;
}

View File

@ -58,6 +58,9 @@ class PollManager : public Actor {
void set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
Promise<Unit> &&promise);
void get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise);
@ -110,6 +113,15 @@ class PollManager : public Actor {
void parse(ParserT &parser);
};
struct PollOptionVoters {
vector<UserId> voter_user_ids;
string next_offset;
vector<Promise<std::pair<int32, vector<UserId>>>> pending_queries;
bool was_invalidated = false; // the list needs to be invalidated when voters are changed
};
static constexpr int32 MAX_GET_POLL_VOTERS = 20; // server side limit
class SetPollAnswerLogEvent;
class StopPollLogEvent;
@ -157,6 +169,15 @@ class PollManager : public Actor {
void on_set_poll_answer(PollId poll_id, uint64 generation, Result<tl_object_ptr<telegram_api::Updates>> &&result);
void invalidate_poll_voters(const Poll *poll, PollId poll_id);
void invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index);
PollOptionVoters &get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id);
void on_get_poll_voters(PollId poll_id, int32 option_id,
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result);
void do_stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
uint64 logevent_id, Promise<Unit> &&promise);
@ -177,6 +198,8 @@ class PollManager : public Actor {
};
std::unordered_map<PollId, PendingPollAnswer, PollIdHash> pending_answers_;
std::unordered_map<PollId, vector<PollOptionVoters>, PollIdHash> poll_voters_;
int64 current_local_poll_id_ = 0;
uint64 current_generation_ = 0;

View File

@ -7253,6 +7253,21 @@ void Td::on_request(uint64 id, td_api::setPollAnswer &request) {
std::move(request.option_ids_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getPollVoters &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda(
[promise = std::move(promise), td = this](Result<std::pair<int32, vector<UserId>>> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(td->contacts_manager_->get_users_object(result.ok().first, result.ok().second));
}
});
messages_manager_->get_poll_voters({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.option_id_,
request.offset_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::stopPoll &request) {
CREATE_OK_REQUEST_PROMISE();
messages_manager_->stop_poll({DialogId(request.chat_id_), MessageId(request.message_id_)},

View File

@ -907,6 +907,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::setPollAnswer &request);
void on_request(uint64 id, td_api::getPollVoters &request);
void on_request(uint64 id, td_api::stopPoll &request);
void on_request(uint64 id, const td_api::getLoginUrlInfo &request);

View File

@ -3489,6 +3489,17 @@ class CliClient final : public Actor {
std::tie(message_id, option_ids) = split(args);
send_request(td_api::make_object<td_api::setPollAnswer>(as_chat_id(chat_id), as_message_id(message_id),
to_integers<int32>(option_ids)));
} else if (op == "gpollv") {
string chat_id;
string message_id;
string option_id;
string offset;
std::tie(chat_id, args) = split(args);
std::tie(message_id, args) = split(args);
std::tie(option_id, offset) = split(args);
send_request(td_api::make_object<td_api::getPollVoters>(as_chat_id(chat_id), as_message_id(message_id),
to_integer<int32>(option_id), to_integer<int32>(offset)));
} else if (op == "stoppoll") {
string chat_id;
string message_id;