Add messagePoll support.

GitOrigin-RevId: 41b93b2708285e4051fc01b856aa14a8c8c5c692
This commit is contained in:
levlam 2019-02-19 16:45:32 +03:00
parent 8f33e737e1
commit 5ef99afec7
18 changed files with 720 additions and 20 deletions

View File

@ -417,6 +417,7 @@ set(TDLIB_SOURCE
td/telegram/PhoneNumberManager.cpp
td/telegram/PrivacyManager.cpp
td/telegram/Photo.cpp
td/telegram/PollManager.cpp
td/telegram/ReplyMarkup.cpp
td/telegram/SecretChatActor.cpp
td/telegram/SecretChatDb.cpp
@ -567,6 +568,8 @@ set(TDLIB_SOURCE
td/telegram/Payments.h
td/telegram/PhoneNumberManager.h
td/telegram/Photo.h
td/telegram/PollId.h
td/telegram/PollManager.h
td/telegram/PrivacyManager.h
td/telegram/PtsManager.h
td/telegram/ReplyMarkup.h
@ -618,6 +621,8 @@ set(TDLIB_SOURCE
td/telegram/NotificationSettings.hpp
td/telegram/Payments.hpp
td/telegram/Photo.hpp
td/telegram/PollId.hpp
td/telegram/PollManager.hpp
td/telegram/ReplyMarkup.hpp
td/telegram/SecureValue.hpp
td/telegram/SendCodeHelper.hpp

View File

@ -192,6 +192,10 @@ maskPointChin = MaskPoint;
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
//@description Describes one answer of a poll @text Answer text, 1-100 characters @voter_count Number of voters for this answer @is_chosen True, if the answer was chosen by the user
pollAnswer text:string voter_count:int32 is_chosen:Bool = PollAnswer;
//@description Describes an animation file. The animation must be encoded in GIF or MPEG4 format @duration Duration of the animation, in seconds; as defined by the sender @width Width of the animation @height Height of the animation
//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file, usually "image/gif" or "video/mp4" @thumbnail Animation thumbnail; may be null @animation File containing the animation
animation duration:int32 width:int32 height:int32 file_name:string mime_type:string thumbnail:photoSize animation:file = Animation;
@ -237,6 +241,9 @@ venue location:location title:string address:string provider:string id:string ty
//@param_description Game description @photo Game photo @animation Game animation; may be null
game id:int64 short_name:string title:string text:formattedText description:string photo:photo animation:animation = Game;
//@description Describes a poll @question Poll question, 1-255 characters @answers List of poll answers @total_voter_count Total number of voters, participating in the poll @is_closed True, if the poll is closed
poll question:string answers:vector<pollAnswer> total_voter_count:int32 is_closed:Bool = Poll;
//@description Describes a user profile photo @id Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of userProfilePhotos
//@small A small (160x160) user profile photo @big A big (640x640) user profile photo
@ -1215,6 +1222,9 @@ messageContact contact:contact = MessageContent;
//@description A message with a game @game Game
messageGame game:game = MessageContent;
//@description A message with a poll @poll Poll
messagePoll poll:poll = MessageContent;
//@description A message with an invoice from a bot @title Product title @param_description Product description @photo Product photo; may be null @currency Currency for the product price @total_amount Product total price in the minimal quantity of the currency
//@start_parameter Unique invoice bot start_parameter. To share an invoice use the URL https://t.me/{bot_username}?start={start_parameter} @is_test True, if the invoice is a test invoice
//@need_shipping_address True, if the shipping address should be specified @receipt_message_id The identifier of the message with the receipt, after the product has been purchased
@ -1392,6 +1402,9 @@ inputMessageGame bot_user_id:int32 game_short_name:string = InputMessageContent;
//@payload The invoice payload @provider_token Payment provider token @provider_data JSON-encoded data about the invoice, which will be shared with the payment provider @start_parameter Unique invoice bot start_parameter for the generation of this invoice
inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string = InputMessageContent;
//@description A message with a poll @question Poll question, 1-255 characters @answers List of poll answers, 1-10 strings 1-100 characters each
inputMessagePoll question:string answers:vector<string> = InputMessageContent;
//@description A forwarded message @from_chat_id Identifier for the chat this forwarded message came from @message_id Identifier of the message to forward @in_game_share True, if a game message should be shared within a launched game; applies only to game messages
inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool = InputMessageContent;

Binary file not shown.

View File

@ -38,6 +38,9 @@
#include "td/telegram/Payments.hpp"
#include "td/telegram/Photo.h"
#include "td/telegram/Photo.hpp"
#include "td/telegram/PollId.h"
#include "td/telegram/PollId.hpp"
#include "td/telegram/PollManager.h"
#include "td/telegram/secret_api.hpp"
#include "td/telegram/SecureValue.h"
#include "td/telegram/SecureValue.hpp"
@ -422,7 +425,7 @@ class MessageChatSetTtl : public MessageContent {
class MessageUnsupported : public MessageContent {
public:
static constexpr int32 CURRENT_VERSION = 2;
static constexpr int32 CURRENT_VERSION = 3;
int32 version = CURRENT_VERSION;
MessageUnsupported() = default;
@ -618,6 +621,19 @@ class MessagePassportDataReceived : public MessageContent {
}
};
class MessagePoll : public MessageContent {
public:
PollId poll_id;
MessagePoll() = default;
explicit MessagePoll(PollId poll_id) : poll_id(poll_id) {
}
MessageContentType get_type() const override {
return MessageContentType::Poll;
}
};
StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type) {
switch (content_type) {
case MessageContentType::None:
@ -702,6 +718,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont
return string_builder << "PassportDataSent";
case MessageContentType::PassportDataReceived:
return string_builder << "PassportDataReceived";
case MessageContentType::Poll:
return string_builder << "Poll";
default:
UNREACHABLE();
return string_builder;
@ -957,6 +975,11 @@ static void store(const MessageContent *content, StorerT &storer) {
store(m->credentials, storer);
break;
}
case MessageContentType::Poll: {
auto m = static_cast<const MessagePoll *>(content);
store(m->poll_id, storer);
break;
}
default:
UNREACHABLE();
}
@ -1283,6 +1306,13 @@ static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
content = std::move(m);
break;
}
case MessageContentType::Poll: {
auto m = make_unique<MessagePoll>();
parse(m->poll_id, parser);
is_bad = !m->poll_id.is_valid();
content = std::move(m);
break;
}
default:
LOG(FATAL) << "Have unknown message content type " << static_cast<int32>(content_type);
}
@ -1715,6 +1745,42 @@ static Result<InputMessageContent> create_input_message_content(
content = std::move(message_invoice);
break;
}
case td_api::inputMessagePoll::ID: {
constexpr size_t MAX_POLL_QUESTION_LENGTH = 255; // server-side limit
constexpr size_t MAX_POLL_ANSWER_LENGTH = 100; // server-side limit
constexpr size_t MAX_POLL_ANSWERS = 10; // server-side limit
auto input_poll = static_cast<td_api::inputMessagePoll *>(input_message_content.get());
if (!clean_input_string(input_poll->question_)) {
return Status::Error(400, "Poll question must be encoded in UTF-8");
}
if (input_poll->question_.empty()) {
return Status::Error(400, "Poll question must be non-empty");
}
if (input_poll->question_.size() > MAX_POLL_QUESTION_LENGTH) {
return Status::Error(400, PSLICE() << "Poll question length must not exceed " << MAX_POLL_QUESTION_LENGTH);
}
if (input_poll->answers_.empty()) {
return Status::Error(400, "Poll must have at least 1 answer");
}
if (input_poll->answers_.size() > MAX_POLL_ANSWERS) {
return Status::Error(400, PSLICE() << "Poll can't have more than " << MAX_POLL_QUESTION_LENGTH << " answers");
}
for (auto &answer : input_poll->answers_) {
if (!clean_input_string(answer)) {
return Status::Error(400, "Poll answers must be encoded in UTF-8");
}
if (answer.empty()) {
return Status::Error(400, "Poll answers must be non-empty");
}
if (answer.size() > MAX_POLL_ANSWER_LENGTH) {
return Status::Error(400, PSLICE() << "Poll answers length must not exceed " << MAX_POLL_ANSWER_LENGTH);
}
}
content = make_unique<MessagePoll>(
td->poll_manager_->create_poll(std::move(input_poll->question_), std::move(input_poll->answers_)));
break;
}
default:
UNREACHABLE();
}
@ -1921,6 +1987,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
break;
default:
UNREACHABLE();
@ -2069,6 +2136,10 @@ static tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageCont
auto m = static_cast<const MessageVoiceNote *>(content);
return td->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file));
}
case MessageContentType::Poll: {
auto m = static_cast<const MessagePoll *>(content);
return td->poll_manager_->get_input_media(m->poll_id);
}
case MessageContentType::Text:
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
@ -2205,6 +2276,7 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) {
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
break;
default:
UNREACHABLE();
@ -2254,6 +2326,7 @@ bool is_allowed_media_group_content(MessageContentType content_type) {
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return false;
default:
UNREACHABLE();
@ -2318,6 +2391,7 @@ bool is_secret_message_content(int32 ttl, MessageContentType content_type) {
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return false;
default:
UNREACHABLE();
@ -2345,6 +2419,7 @@ bool is_service_message_content(MessageContentType content_type) {
case MessageContentType::VoiceNote:
case MessageContentType::ExpiredPhoto:
case MessageContentType::ExpiredVideo:
case MessageContentType::Poll:
return false;
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
@ -2418,6 +2493,7 @@ bool can_have_message_content_caption(MessageContentType content_type) {
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return false;
default:
UNREACHABLE();
@ -2529,6 +2605,7 @@ int32 get_message_content_index_mask(const MessageContent *content, const Td *td
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return 0;
default:
UNREACHABLE();
@ -3040,6 +3117,18 @@ void merge_message_contents(Td *td, MessageContent *old_content, MessageContent
}
break;
}
case MessageContentType::Poll: {
auto old_ = static_cast<const MessagePoll *>(old_content);
auto new_ = static_cast<const MessagePoll *>(new_content);
if (old_->poll_id != new_->poll_id) {
if (old_->poll_id.get() > 0) {
LOG(ERROR) << "Poll id has changed from " << old_->poll_id << " to " << new_->poll_id;
}
// polls are updated in a different way
is_content_changed = true;
}
break;
}
case MessageContentType::Unsupported: {
auto old_ = static_cast<const MessageUnsupported *>(old_content);
auto new_ = static_cast<const MessageUnsupported *>(new_content);
@ -3170,6 +3259,7 @@ bool merge_message_content_file_id(Td *td, MessageContent *message_content, File
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type;
break;
default:
@ -3743,10 +3833,17 @@ unique_ptr<MessageContent> get_message_content(Td *td, FormattedText message,
auto web_page_id = td->web_pages_manager_->on_get_web_page(std::move(media_web_page->webpage_), owner_dialog_id);
return make_unique<MessageText>(std::move(message), web_page_id);
}
case telegram_api::messageMediaUnsupported::ID: {
return make_unique<MessageUnsupported>();
case telegram_api::messageMediaPoll::ID: {
auto media_poll = move_tl_object_as<telegram_api::messageMediaPoll>(media);
auto poll_id =
td->poll_manager_->on_get_poll(PollId(), std::move(media_poll->poll_), std::move(media_poll->results_));
if (!poll_id.is_valid()) {
break;
}
return make_unique<MessagePoll>(poll_id);
}
case telegram_api::messageMediaUnsupported::ID:
return make_unique<MessageUnsupported>();
default:
UNREACHABLE();
}
@ -3917,6 +4014,8 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
CHECK(result->file_id.is_valid());
return std::move(result);
}
case MessageContentType::Poll:
return make_unique<MessagePoll>(*static_cast<const MessagePoll *>(content));
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
@ -4310,6 +4409,10 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
get_encrypted_passport_element_object(td->file_manager_.get(), m->values),
get_encrypted_credentials_object(m->credentials));
}
case MessageContentType::Poll: {
const MessagePoll *m = static_cast<const MessagePoll *>(content);
return make_tl_object<td_api::messagePoll>(td->poll_manager_->get_poll_object(m->poll_id));
}
default:
UNREACHABLE();
return nullptr;
@ -4586,6 +4689,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return string();
default:
UNREACHABLE();
@ -4758,6 +4862,8 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC
break;
case MessageContentType::PassportDataReceived:
break;
case MessageContentType::Poll:
break;
default:
UNREACHABLE();
break;

View File

@ -74,7 +74,8 @@ enum class MessageContentType : int32 {
CustomServiceAction,
WebsiteConnected,
PassportDataSent,
PassportDataReceived
PassportDataReceived,
Poll
};
StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type);

View File

@ -5185,6 +5185,7 @@ bool MessagesManager::need_cancel_user_dialog_action(int32 action_id, MessageCon
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
return false;
default:
UNREACHABLE();
@ -9510,10 +9511,8 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
}
if (sender_user_id.is_valid() && (sender_user_id == my_id && dialog_id != my_dialog_id) != is_outgoing) {
// if (content->get_type() != MessageContentType::ChatAddUser) { // TODO: we have wrong flags for invites via links
LOG(ERROR) << "Receive wrong message out flag: me is " << my_id << ", message is from " << sender_user_id
<< ", flags = " << flags << " for " << message_id << " in " << dialog_id;
// }
is_outgoing = !is_outgoing;
if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) &&
@ -14825,6 +14824,7 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
bool can_send_stickers = true;
bool can_send_animations = true;
bool can_send_games = true;
bool can_send_polls = true;
switch (dialog_type) {
case DialogType::User:
@ -14838,10 +14838,12 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
can_send_stickers = channel_status.can_send_stickers();
can_send_animations = channel_status.can_send_animations();
can_send_games = channel_status.can_send_games();
// can_send_polls = channel_status.can_send_polls(); TODO
break;
}
case DialogType::SecretChat:
can_send_games = false;
can_send_polls = false;
break;
case DialogType::None:
default:
@ -14924,6 +14926,11 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
return Status::Error(400, "Not enough rights to send photos to the chat");
}
break;
case MessageContentType::Poll:
if (!can_send_polls) {
return Status::Error(400, "Not enough rights to send polls to the chat");
}
break;
case MessageContentType::Sticker:
if (!can_send_stickers) {
return Status::Error(400, "Not enough rights to send stickers to the chat");
@ -16225,6 +16232,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
}
case MessageContentType::Contact:
case MessageContentType::Location:
case MessageContentType::Poll:
case MessageContentType::Sticker:
case MessageContentType::Venue:
case MessageContentType::VideoNote:
@ -19275,6 +19283,8 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
error_message = "Wrong game short name specified";
} else if (content_type == MessageContentType::Invoice) {
error_message = "Wrong invoice information specified";
} else if (content_type == MessageContentType::Poll) {
error_message = "Wrong poll data specified";
} else {
error_message = "Wrong file identifier/HTTP URL specified";
}

56
td/telegram/PollId.h Normal file
View File

@ -0,0 +1,56 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
#include <functional>
#include <type_traits>
namespace td {
class PollId {
int64 id = 0;
public:
PollId() = default;
explicit PollId(int64 poll_id) : id(poll_id) {
}
template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
PollId(T poll_id) = delete;
int64 get() const {
return id;
}
bool operator==(const PollId &other) const {
return id == other.id;
}
bool operator!=(const PollId &other) const {
return id != other.id;
}
bool is_valid() const {
return id != 0;
}
};
struct PollIdHash {
std::size_t operator()(PollId poll_id) const {
return std::hash<int64>()(poll_id.get());
}
};
inline StringBuilder &operator<<(StringBuilder &string_builder, PollId poll_id) {
return string_builder << "poll " << poll_id.get();
}
} // namespace td

27
td/telegram/PollId.hpp Normal file
View File

@ -0,0 +1,27 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/PollId.h"
#include "td/telegram/PollManager.h"
#include "td/telegram/PollManager.hpp"
#include "td/telegram/Td.h"
namespace td {
template <class StorerT>
void store(const PollId &poll_id, StorerT &storer) {
storer.context()->td().get_actor_unsafe()->poll_manager_->store_poll(poll_id, storer);
}
template <class ParserT>
void parse(PollId &poll_id, ParserT &parser) {
poll_id = parser.context()->td().get_actor_unsafe()->poll_manager_->parse_poll(parser);
}
} // namespace td

254
td/telegram/PollManager.cpp Normal file
View File

@ -0,0 +1,254 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/PollManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/PollManager.hpp"
#include "td/telegram/TdDb.h"
#include "td/telegram/Td.h"
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueAsync.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Status.h"
namespace td {
PollManager::PollManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}
void PollManager::tear_down() {
parent_.reset();
}
PollManager::~PollManager() = default;
bool PollManager::is_local_poll_id(PollId poll_id) {
return poll_id.get() < 0 && poll_id.get() > std::numeric_limits<int32>::min();
}
const PollManager::Poll *PollManager::get_poll(PollId poll_id) const {
auto p = polls_.find(poll_id);
if (p == polls_.end()) {
return nullptr;
} else {
return p->second.get();
}
}
PollManager::Poll *PollManager::get_poll_editable(PollId poll_id) {
auto p = polls_.find(poll_id);
if (p == polls_.end()) {
return nullptr;
} else {
return p->second.get();
}
}
bool PollManager::have_poll(PollId poll_id) const {
return get_poll(poll_id) != nullptr;
}
string PollManager::get_poll_database_key(PollId poll_id) {
return PSTRING() << "poll" << poll_id.get();
}
void PollManager::save_poll(const Poll *poll, PollId poll_id) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(!is_local_poll_id(poll_id));
LOG(INFO) << "Save " << poll_id << " to database";
CHECK(poll != nullptr);
// G()->td_db()->get_sqlite_pmc()->set(get_poll_database_key(poll_id), log_event_store(*poll).as_slice().str(), Auto());
}
void PollManager::on_load_poll_from_database(PollId poll_id, string value) {
loaded_from_database_polls_.insert(poll_id);
LOG(INFO) << "Successfully loaded " << poll_id << " of size " << value.size() << " from database";
// G()->td_db()->get_sqlite_pmc()->erase(get_poll_database_key(poll_id), Auto());
// return;
CHECK(!have_poll(poll_id));
if (!value.empty()) {
auto result = make_unique<Poll>();
auto status = log_event_parse(*result, value);
if (status.is_error()) {
LOG(FATAL) << status << ": " << format::as_hex_dump<4>(Slice(value));
}
polls_[poll_id] = std::move(result);
}
}
bool PollManager::have_poll_force(PollId poll_id) {
return get_poll_force(poll_id) != nullptr;
}
PollManager::Poll *PollManager::get_poll_force(PollId poll_id) {
auto poll = get_poll_editable(poll_id);
if (poll != nullptr) {
return poll;
}
if (!G()->parameters().use_message_db) {
return nullptr;
}
if (loaded_from_database_polls_.count(poll_id)) {
return nullptr;
}
LOG(INFO) << "Trying to load " << poll_id << " from database";
on_load_poll_from_database(poll_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_poll_database_key(poll_id)));
return get_poll_editable(poll_id);
}
td_api::object_ptr<td_api::pollAnswer> PollManager::get_poll_answer_object(const PollAnswer &poll_answer) {
return td_api::make_object<td_api::pollAnswer>(poll_answer.text, poll_answer.voter_count, poll_answer.is_chosen);
}
td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return td_api::make_object<td_api::poll>(poll->question, transform(poll->answers, get_poll_answer_object),
poll->total_voter_count, poll->is_closed);
}
telegram_api::object_ptr<telegram_api::pollAnswer> PollManager::get_input_poll_answer(const PollAnswer &poll_answer) {
return telegram_api::make_object<telegram_api::pollAnswer>(poll_answer.text, BufferSlice(poll_answer.data));
}
PollId PollManager::create_poll(string &&question, vector<string> &&answers) {
auto poll = make_unique<Poll>();
poll->question = std::move(question);
int pos = 0;
for (auto &answer_text : answers) {
PollAnswer answer;
answer.text = std::move(answer_text);
answer.data = to_string(pos++);
poll->answers.push_back(std::move(answer));
}
PollId poll_id(--current_local_poll_id_);
CHECK(is_local_poll_id(poll_id));
bool is_inserted = polls_.emplace(poll_id, std::move(poll)).second;
CHECK(is_inserted);
return poll_id;
}
tl_object_ptr<telegram_api::InputMedia> PollManager::get_input_media(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return telegram_api::make_object<telegram_api::inputMediaPoll>(telegram_api::make_object<telegram_api::poll>(
0, 0, false /* ignored */, poll->question, transform(poll->answers, get_input_poll_answer)));
}
vector<PollManager::PollAnswer> PollManager::get_poll_answers(
vector<tl_object_ptr<telegram_api::pollAnswer>> &&poll_answers) {
return transform(std::move(poll_answers), [](tl_object_ptr<telegram_api::pollAnswer> &&poll_answer) {
PollAnswer answer;
answer.text = std::move(poll_answer->text_);
answer.data = poll_answer->option_.as_slice().str();
return answer;
});
}
PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll> &&poll_server,
tl_object_ptr<telegram_api::pollResults> &&poll_results) {
if (!poll_id.is_valid() && poll_server != nullptr) {
poll_id = PollId(poll_server->id_);
}
if (!poll_id.is_valid() || is_local_poll_id(poll_id)) {
LOG(ERROR) << "Receive " << poll_id << " from server";
return PollId();
}
if (poll_server != nullptr && poll_server->id_ != poll_id.get()) {
LOG(ERROR) << "Receive poll " << poll_server->id_ << " instead of " << poll_id;
return PollId();
}
auto poll = get_poll_force(poll_id);
bool is_changed = false;
if (poll == nullptr) {
if (poll_server == nullptr) {
LOG(INFO) << "Ignore " << poll_id << ", because have no data about it";
return PollId();
}
auto p = make_unique<Poll>();
poll = p.get();
bool is_inserted = polls_.emplace(poll_id, std::move(p)).second;
CHECK(is_inserted);
}
CHECK(poll != nullptr);
if (poll_server != nullptr) {
if (poll->question != poll_server->question_) {
poll->question = std::move(poll_server->question_);
is_changed = true;
}
if (poll->answers.size() != poll_server->answers_.size()) {
poll->answers = get_poll_answers(std::move(poll_server->answers_));
is_changed = true;
} else {
for (size_t i = 0; i < poll->answers.size(); i++) {
if (poll->answers[i].text != poll_server->answers_[i]->text_) {
poll->answers[i].text = std::move(poll_server->answers_[i]->text_);
is_changed = true;
}
if (poll->answers[i].data != poll_server->answers_[i]->option_.as_slice()) {
poll->answers[i].data = poll_server->answers_[i]->option_.as_slice().str();
poll->answers[i].voter_count = 0;
poll->answers[i].is_chosen = false;
is_changed = true;
}
}
}
bool is_closed = (poll_server->flags_ & telegram_api::poll::CLOSED_MASK) != 0;
if (is_closed != poll->is_closed) {
poll->is_closed = is_closed;
is_changed = true;
}
}
CHECK(poll_results != nullptr);
bool is_min = (poll_results->flags_ & telegram_api::pollResults::MIN_MASK) != 0;
if ((poll_results->flags_ & telegram_api::pollResults::TOTAL_VOTERS_MASK) != 0 &&
poll_results->total_voters_ != poll->total_voter_count) {
poll->total_voter_count = poll_results->total_voters_;
is_changed = true;
}
for (auto &poll_result : poll_results->results_) {
Slice data = poll_result->option_.as_slice();
for (auto &answer : poll->answers) {
if (answer.data != data) {
continue;
}
if (!is_min) {
bool is_chosen = (poll_result->flags_ & telegram_api::pollAnswerVoters::CHOSEN_MASK) != 0;
if (is_chosen != answer.is_chosen) {
answer.is_chosen = is_chosen;
is_changed = true;
}
}
if (poll_result->voters_ != answer.voter_count) {
answer.voter_count = poll_result->voters_;
is_changed = true;
}
}
}
if (is_changed) {
save_poll(poll, poll_id);
}
return poll_id;
}
} // namespace td

110
td/telegram/PollManager.h Normal file
View File

@ -0,0 +1,110 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/PollId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include <unordered_map>
#include <unordered_set>
namespace td {
class Td;
class PollManager : public Actor {
public:
PollManager(Td *td, ActorShared<> parent);
PollManager(const PollManager &) = delete;
PollManager &operator=(const PollManager &) = delete;
PollManager(PollManager &&) = delete;
PollManager &operator=(PollManager &&) = delete;
~PollManager() override;
PollId create_poll(string &&question, vector<string> &&answers);
tl_object_ptr<telegram_api::InputMedia> get_input_media(PollId poll_id) const;
PollId on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll> &&poll_server,
tl_object_ptr<telegram_api::pollResults> &&poll_results);
td_api::object_ptr<td_api::poll> get_poll_object(PollId poll_id) const;
template <class StorerT>
void store_poll(PollId poll_id, StorerT &storer) const;
template <class ParserT>
PollId parse_poll(ParserT &parser);
private:
struct PollAnswer {
string text;
string data;
int32 voter_count = 0;
bool is_chosen = false;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
struct Poll {
string question;
vector<PollAnswer> answers;
int32 total_voter_count = 0;
bool is_closed = false;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
void tear_down() override;
static bool is_local_poll_id(PollId poll_id);
static td_api::object_ptr<td_api::pollAnswer> get_poll_answer_object(const PollAnswer &poll_answer);
static telegram_api::object_ptr<telegram_api::pollAnswer> get_input_poll_answer(const PollAnswer &poll_answer);
static vector<PollAnswer> get_poll_answers(vector<tl_object_ptr<telegram_api::pollAnswer>> &&poll_answers);
bool have_poll(PollId poll_id) const;
bool have_poll_force(PollId poll_id);
const Poll *get_poll(PollId poll_id) const;
Poll *get_poll_editable(PollId poll_id);
static string get_poll_database_key(PollId poll_id);
void save_poll(const Poll *poll, PollId poll_id);
void on_load_poll_from_database(PollId poll_id, string value);
Poll *get_poll_force(PollId poll_id);
Td *td_;
ActorShared<> parent_;
std::unordered_map<PollId, unique_ptr<Poll>, PollIdHash> polls_;
int64 current_local_poll_id_ = 0;
std::unordered_set<PollId, PollIdHash> loaded_from_database_polls_;
};
} // namespace td

View File

@ -0,0 +1,97 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/PollManager.h"
#include "td/utils/common.h"
#include "td/utils/misc.h"
#include "td/utils/tl_helpers.h"
namespace td {
template <class StorerT>
void PollManager::PollAnswer::store(StorerT &storer) const {
using ::td::store;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_chosen);
END_STORE_FLAGS();
store(text, storer);
store(data, storer);
store(voter_count, storer);
}
template <class ParserT>
void PollManager::PollAnswer::parse(ParserT &parser) {
using ::td::parse;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_chosen);
END_PARSE_FLAGS();
parse(text, parser);
parse(data, parser);
parse(voter_count, parser);
}
template <class StorerT>
void PollManager::Poll::store(StorerT &storer) const {
using ::td::store;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_closed);
END_STORE_FLAGS();
store(question, storer);
store(answers, storer);
store(total_voter_count, storer);
}
template <class ParserT>
void PollManager::Poll::parse(ParserT &parser) {
using ::td::parse;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_closed);
END_PARSE_FLAGS();
parse(question, parser);
parse(answers, parser);
parse(total_voter_count, parser);
}
template <class StorerT>
void PollManager::store_poll(PollId poll_id, StorerT &storer) const {
td::store(poll_id.get(), storer);
if (is_local_poll_id(poll_id)) {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
store(poll->question, storer);
vector<string> answers = transform(poll->answers, [](const PollAnswer &answer) { return answer.text; });
store(answers, storer);
}
}
template <class ParserT>
PollId PollManager::parse_poll(ParserT &parser) {
int64 poll_id_int;
td::parse(poll_id_int, parser);
PollId poll_id(poll_id_int);
if (is_local_poll_id(poll_id)) {
string question;
vector<string> answers;
parse(question, parser);
parse(answers, parser);
return create_poll(std::move(question), std::move(answers));
}
auto poll = get_poll_force(poll_id);
if (poll == nullptr) {
return PollId();
}
return poll_id;
}
} // namespace td

View File

@ -55,6 +55,7 @@
#include "td/telegram/Payments.h"
#include "td/telegram/PhoneNumberManager.h"
#include "td/telegram/Photo.h"
#include "td/telegram/PollManager.h"
#include "td/telegram/PrivacyManager.h"
#include "td/telegram/RequestActor.h"
#include "td/telegram/SecretChatId.h"
@ -3679,6 +3680,8 @@ void Td::dec_actor_refcnt() {
LOG(DEBUG) << "MessagesManager was cleared " << timer;
notification_manager_.reset();
LOG(DEBUG) << "NotificationManager was cleared " << timer;
poll_manager_.reset();
LOG(DEBUG) << "PollManager was cleared " << timer;
stickers_manager_.reset();
LOG(DEBUG) << "StickersManager was cleared " << timer;
updates_manager_.reset();
@ -3854,6 +3857,8 @@ void Td::clear() {
LOG(DEBUG) << "MessagesManager actor was cleared " << timer;
notification_manager_actor_.reset();
LOG(DEBUG) << "NotificationManager actor was cleared " << timer;
poll_manager_actor_.reset();
LOG(DEBUG) << "PollManager actor was cleared " << timer;
stickers_manager_actor_.reset();
LOG(DEBUG) << "StickersManager actor was cleared " << timer;
updates_manager_actor_.reset();
@ -4182,6 +4187,8 @@ Status Td::init(DbKey key) {
G()->set_messages_manager(messages_manager_actor_.get());
notification_manager_ = make_unique<NotificationManager>(this, create_reference());
notification_manager_actor_ = register_actor("NotificationManager", notification_manager_.get());
poll_manager_ = make_unique<PollManager>(this, create_reference());
poll_manager_actor_ = register_actor("PollManager", poll_manager_.get());
G()->set_notification_manager(notification_manager_actor_.get());
stickers_manager_ = make_unique<StickersManager>(this, create_reference());
stickers_manager_actor_ = register_actor("StickersManager", stickers_manager_.get());

View File

@ -54,6 +54,7 @@ class NetStatsManager;
class NotificationManager;
class PasswordManager;
class PhoneNumberManager;
class PollManager;
class PrivacyManager;
class SecureManager;
class SecretChatsManager;
@ -149,6 +150,8 @@ class Td final : public NetQueryCallback {
ActorOwn<MessagesManager> messages_manager_actor_;
unique_ptr<NotificationManager> notification_manager_;
ActorOwn<NotificationManager> notification_manager_actor_;
unique_ptr<PollManager> poll_manager_;
ActorOwn<PollManager> poll_manager_actor_;
unique_ptr<StickersManager> stickers_manager_;
ActorOwn<StickersManager> stickers_manager_actor_;
unique_ptr<UpdatesManager> updates_manager_;

View File

@ -27,6 +27,7 @@
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/NotificationManager.h"
#include "td/telegram/Payments.h"
#include "td/telegram/PollManager.h"
#include "td/telegram/PrivacyManager.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretChatsManager.h"
@ -1869,9 +1870,10 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLangPack> updat
std::move(update->difference_));
}
// unsupported updates
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, bool /*force_apply*/) {
td_->poll_manager_->on_get_poll(PollId(update->poll_id_), std::move(update->poll_), std::move(update->results_));
}
// unsupported updates
} // namespace td

View File

@ -280,8 +280,9 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updateLangPackTooLong> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateLangPack> update, bool /*force_apply*/);
// unsupported updates
void on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, bool /*force_apply*/);
// unsupported updates
};
} // namespace td

View File

@ -21,10 +21,10 @@ class WebPageId {
public:
WebPageId() = default;
explicit WebPageId(int64 webpage_id) : id(webpage_id) {
explicit WebPageId(int64 web_page_id) : id(web_page_id) {
}
template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
WebPageId(T webpage_id) = delete;
WebPageId(T web_page_id) = delete;
int64 get() const {
return id;
@ -54,13 +54,13 @@ class WebPageId {
};
struct WebPageIdHash {
std::size_t operator()(WebPageId webpage_id) const {
return std::hash<int64>()(webpage_id.get());
std::size_t operator()(WebPageId web_page_id) const {
return std::hash<int64>()(web_page_id.get());
}
};
inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId webpage_id) {
return string_builder << "web page " << webpage_id.get();
inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId web_page_id) {
return string_builder << "web page " << web_page_id.get();
}
} // namespace td

View File

@ -2975,6 +2975,14 @@ class CliClient final : public Actor {
send_message(chat_id, make_tl_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
to_integer<int32>(period)));
} else if (op == "spoll") {
string chat_id;
string question;
std::tie(chat_id, args) = split(args);
std::tie(question, args) = split(args);
auto answers = full_split(args);
send_message(chat_id, make_tl_object<td_api::inputMessagePoll>(question, std::move(answers)));
} else if (op == "sp") {
string chat_id;
string photo_path;

View File

@ -441,11 +441,11 @@ class FileManager : public FileLoadManager::Callback {
static bool extract_was_uploaded(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo);
static string extract_file_reference(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo);
template <class T>
void store_file(FileId file_id, T &storer, int32 ttl = 5) const;
template <class StorerT>
void store_file(FileId file_id, StorerT &storer, int32 ttl = 5) const;
template <class T>
FileId parse_file(T &parser);
template <class StorerT>
FileId parse_file(StorerT &parser);
private:
static constexpr char PERSISTENT_ID_VERSION = 2;