Add messagePoll support.
GitOrigin-RevId: 41b93b2708285e4051fc01b856aa14a8c8c5c692
This commit is contained in:
parent
8f33e737e1
commit
5ef99afec7
@ -417,6 +417,7 @@ set(TDLIB_SOURCE
|
|||||||
td/telegram/PhoneNumberManager.cpp
|
td/telegram/PhoneNumberManager.cpp
|
||||||
td/telegram/PrivacyManager.cpp
|
td/telegram/PrivacyManager.cpp
|
||||||
td/telegram/Photo.cpp
|
td/telegram/Photo.cpp
|
||||||
|
td/telegram/PollManager.cpp
|
||||||
td/telegram/ReplyMarkup.cpp
|
td/telegram/ReplyMarkup.cpp
|
||||||
td/telegram/SecretChatActor.cpp
|
td/telegram/SecretChatActor.cpp
|
||||||
td/telegram/SecretChatDb.cpp
|
td/telegram/SecretChatDb.cpp
|
||||||
@ -567,6 +568,8 @@ set(TDLIB_SOURCE
|
|||||||
td/telegram/Payments.h
|
td/telegram/Payments.h
|
||||||
td/telegram/PhoneNumberManager.h
|
td/telegram/PhoneNumberManager.h
|
||||||
td/telegram/Photo.h
|
td/telegram/Photo.h
|
||||||
|
td/telegram/PollId.h
|
||||||
|
td/telegram/PollManager.h
|
||||||
td/telegram/PrivacyManager.h
|
td/telegram/PrivacyManager.h
|
||||||
td/telegram/PtsManager.h
|
td/telegram/PtsManager.h
|
||||||
td/telegram/ReplyMarkup.h
|
td/telegram/ReplyMarkup.h
|
||||||
@ -618,6 +621,8 @@ set(TDLIB_SOURCE
|
|||||||
td/telegram/NotificationSettings.hpp
|
td/telegram/NotificationSettings.hpp
|
||||||
td/telegram/Payments.hpp
|
td/telegram/Payments.hpp
|
||||||
td/telegram/Photo.hpp
|
td/telegram/Photo.hpp
|
||||||
|
td/telegram/PollId.hpp
|
||||||
|
td/telegram/PollManager.hpp
|
||||||
td/telegram/ReplyMarkup.hpp
|
td/telegram/ReplyMarkup.hpp
|
||||||
td/telegram/SecureValue.hpp
|
td/telegram/SecureValue.hpp
|
||||||
td/telegram/SendCodeHelper.hpp
|
td/telegram/SendCodeHelper.hpp
|
||||||
|
@ -192,6 +192,10 @@ maskPointChin = MaskPoint;
|
|||||||
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
|
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
|
//@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
|
//@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;
|
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
|
//@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;
|
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
|
//@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
|
//@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
|
//@description A message with a game @game Game
|
||||||
messageGame game:game = MessageContent;
|
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
|
//@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
|
//@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
|
//@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
|
//@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;
|
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
|
//@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;
|
inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool = InputMessageContent;
|
||||||
|
|
||||||
|
Binary file not shown.
@ -38,6 +38,9 @@
|
|||||||
#include "td/telegram/Payments.hpp"
|
#include "td/telegram/Payments.hpp"
|
||||||
#include "td/telegram/Photo.h"
|
#include "td/telegram/Photo.h"
|
||||||
#include "td/telegram/Photo.hpp"
|
#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/secret_api.hpp"
|
||||||
#include "td/telegram/SecureValue.h"
|
#include "td/telegram/SecureValue.h"
|
||||||
#include "td/telegram/SecureValue.hpp"
|
#include "td/telegram/SecureValue.hpp"
|
||||||
@ -422,7 +425,7 @@ class MessageChatSetTtl : public MessageContent {
|
|||||||
|
|
||||||
class MessageUnsupported : public MessageContent {
|
class MessageUnsupported : public MessageContent {
|
||||||
public:
|
public:
|
||||||
static constexpr int32 CURRENT_VERSION = 2;
|
static constexpr int32 CURRENT_VERSION = 3;
|
||||||
int32 version = CURRENT_VERSION;
|
int32 version = CURRENT_VERSION;
|
||||||
|
|
||||||
MessageUnsupported() = default;
|
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) {
|
StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type) {
|
||||||
switch (content_type) {
|
switch (content_type) {
|
||||||
case MessageContentType::None:
|
case MessageContentType::None:
|
||||||
@ -702,6 +718,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont
|
|||||||
return string_builder << "PassportDataSent";
|
return string_builder << "PassportDataSent";
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
return string_builder << "PassportDataReceived";
|
return string_builder << "PassportDataReceived";
|
||||||
|
case MessageContentType::Poll:
|
||||||
|
return string_builder << "Poll";
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
return string_builder;
|
return string_builder;
|
||||||
@ -957,6 +975,11 @@ static void store(const MessageContent *content, StorerT &storer) {
|
|||||||
store(m->credentials, storer);
|
store(m->credentials, storer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MessageContentType::Poll: {
|
||||||
|
auto m = static_cast<const MessagePoll *>(content);
|
||||||
|
store(m->poll_id, storer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@ -1283,6 +1306,13 @@ static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
|
|||||||
content = std::move(m);
|
content = std::move(m);
|
||||||
break;
|
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:
|
default:
|
||||||
LOG(FATAL) << "Have unknown message content type " << static_cast<int32>(content_type);
|
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);
|
content = std::move(message_invoice);
|
||||||
break;
|
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:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@ -1921,6 +1987,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -2069,6 +2136,10 @@ static tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageCont
|
|||||||
auto m = static_cast<const MessageVoiceNote *>(content);
|
auto m = static_cast<const MessageVoiceNote *>(content);
|
||||||
return td->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file));
|
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::Text:
|
||||||
case MessageContentType::Unsupported:
|
case MessageContentType::Unsupported:
|
||||||
case MessageContentType::ChatCreate:
|
case MessageContentType::ChatCreate:
|
||||||
@ -2205,6 +2276,7 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) {
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -2254,6 +2326,7 @@ bool is_allowed_media_group_content(MessageContentType content_type) {
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -2318,6 +2391,7 @@ bool is_secret_message_content(int32 ttl, MessageContentType content_type) {
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -2345,6 +2419,7 @@ bool is_service_message_content(MessageContentType content_type) {
|
|||||||
case MessageContentType::VoiceNote:
|
case MessageContentType::VoiceNote:
|
||||||
case MessageContentType::ExpiredPhoto:
|
case MessageContentType::ExpiredPhoto:
|
||||||
case MessageContentType::ExpiredVideo:
|
case MessageContentType::ExpiredVideo:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return false;
|
return false;
|
||||||
case MessageContentType::ChatCreate:
|
case MessageContentType::ChatCreate:
|
||||||
case MessageContentType::ChatChangeTitle:
|
case MessageContentType::ChatChangeTitle:
|
||||||
@ -2418,6 +2493,7 @@ bool can_have_message_content_caption(MessageContentType content_type) {
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -2529,6 +2605,7 @@ int32 get_message_content_index_mask(const MessageContent *content, const Td *td
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return 0;
|
return 0;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -3040,6 +3117,18 @@ void merge_message_contents(Td *td, MessageContent *old_content, MessageContent
|
|||||||
}
|
}
|
||||||
break;
|
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: {
|
case MessageContentType::Unsupported: {
|
||||||
auto old_ = static_cast<const MessageUnsupported *>(old_content);
|
auto old_ = static_cast<const MessageUnsupported *>(old_content);
|
||||||
auto new_ = static_cast<const MessageUnsupported *>(new_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::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type;
|
LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type;
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
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);
|
return make_unique<MessageText>(std::move(message), web_page_id);
|
||||||
}
|
}
|
||||||
|
case telegram_api::messageMediaPoll::ID: {
|
||||||
case telegram_api::messageMediaUnsupported::ID: {
|
auto media_poll = move_tl_object_as<telegram_api::messageMediaPoll>(media);
|
||||||
return make_unique<MessageUnsupported>();
|
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:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@ -3917,6 +4014,8 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
|
|||||||
CHECK(result->file_id.is_valid());
|
CHECK(result->file_id.is_valid());
|
||||||
return std::move(result);
|
return std::move(result);
|
||||||
}
|
}
|
||||||
|
case MessageContentType::Poll:
|
||||||
|
return make_unique<MessagePoll>(*static_cast<const MessagePoll *>(content));
|
||||||
case MessageContentType::Unsupported:
|
case MessageContentType::Unsupported:
|
||||||
case MessageContentType::ChatCreate:
|
case MessageContentType::ChatCreate:
|
||||||
case MessageContentType::ChatChangeTitle:
|
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_passport_element_object(td->file_manager_.get(), m->values),
|
||||||
get_encrypted_credentials_object(m->credentials));
|
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:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -4586,6 +4689,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return string();
|
return string();
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -4758,6 +4862,8 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC
|
|||||||
break;
|
break;
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
break;
|
break;
|
||||||
|
case MessageContentType::Poll:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
break;
|
break;
|
||||||
|
@ -74,7 +74,8 @@ enum class MessageContentType : int32 {
|
|||||||
CustomServiceAction,
|
CustomServiceAction,
|
||||||
WebsiteConnected,
|
WebsiteConnected,
|
||||||
PassportDataSent,
|
PassportDataSent,
|
||||||
PassportDataReceived
|
PassportDataReceived,
|
||||||
|
Poll
|
||||||
};
|
};
|
||||||
|
|
||||||
StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type);
|
StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type);
|
||||||
|
@ -5185,6 +5185,7 @@ bool MessagesManager::need_cancel_user_dialog_action(int32 action_id, MessageCon
|
|||||||
case MessageContentType::WebsiteConnected:
|
case MessageContentType::WebsiteConnected:
|
||||||
case MessageContentType::PassportDataSent:
|
case MessageContentType::PassportDataSent:
|
||||||
case MessageContentType::PassportDataReceived:
|
case MessageContentType::PassportDataReceived:
|
||||||
|
case MessageContentType::Poll:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
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 (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
|
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;
|
<< ", flags = " << flags << " for " << message_id << " in " << dialog_id;
|
||||||
// }
|
|
||||||
is_outgoing = !is_outgoing;
|
is_outgoing = !is_outgoing;
|
||||||
|
|
||||||
if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) &&
|
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_stickers = true;
|
||||||
bool can_send_animations = true;
|
bool can_send_animations = true;
|
||||||
bool can_send_games = true;
|
bool can_send_games = true;
|
||||||
|
bool can_send_polls = true;
|
||||||
|
|
||||||
switch (dialog_type) {
|
switch (dialog_type) {
|
||||||
case DialogType::User:
|
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_stickers = channel_status.can_send_stickers();
|
||||||
can_send_animations = channel_status.can_send_animations();
|
can_send_animations = channel_status.can_send_animations();
|
||||||
can_send_games = channel_status.can_send_games();
|
can_send_games = channel_status.can_send_games();
|
||||||
|
// can_send_polls = channel_status.can_send_polls(); TODO
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DialogType::SecretChat:
|
case DialogType::SecretChat:
|
||||||
can_send_games = false;
|
can_send_games = false;
|
||||||
|
can_send_polls = false;
|
||||||
break;
|
break;
|
||||||
case DialogType::None:
|
case DialogType::None:
|
||||||
default:
|
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");
|
return Status::Error(400, "Not enough rights to send photos to the chat");
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case MessageContentType::Sticker:
|
||||||
if (!can_send_stickers) {
|
if (!can_send_stickers) {
|
||||||
return Status::Error(400, "Not enough rights to send stickers to the chat");
|
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::Contact:
|
||||||
case MessageContentType::Location:
|
case MessageContentType::Location:
|
||||||
|
case MessageContentType::Poll:
|
||||||
case MessageContentType::Sticker:
|
case MessageContentType::Sticker:
|
||||||
case MessageContentType::Venue:
|
case MessageContentType::Venue:
|
||||||
case MessageContentType::VideoNote:
|
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";
|
error_message = "Wrong game short name specified";
|
||||||
} else if (content_type == MessageContentType::Invoice) {
|
} else if (content_type == MessageContentType::Invoice) {
|
||||||
error_message = "Wrong invoice information specified";
|
error_message = "Wrong invoice information specified";
|
||||||
|
} else if (content_type == MessageContentType::Poll) {
|
||||||
|
error_message = "Wrong poll data specified";
|
||||||
} else {
|
} else {
|
||||||
error_message = "Wrong file identifier/HTTP URL specified";
|
error_message = "Wrong file identifier/HTTP URL specified";
|
||||||
}
|
}
|
||||||
|
56
td/telegram/PollId.h
Normal file
56
td/telegram/PollId.h
Normal 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
27
td/telegram/PollId.hpp
Normal 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
254
td/telegram/PollManager.cpp
Normal 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
110
td/telegram/PollManager.h
Normal 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
|
97
td/telegram/PollManager.hpp
Normal file
97
td/telegram/PollManager.hpp
Normal 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
|
@ -55,6 +55,7 @@
|
|||||||
#include "td/telegram/Payments.h"
|
#include "td/telegram/Payments.h"
|
||||||
#include "td/telegram/PhoneNumberManager.h"
|
#include "td/telegram/PhoneNumberManager.h"
|
||||||
#include "td/telegram/Photo.h"
|
#include "td/telegram/Photo.h"
|
||||||
|
#include "td/telegram/PollManager.h"
|
||||||
#include "td/telegram/PrivacyManager.h"
|
#include "td/telegram/PrivacyManager.h"
|
||||||
#include "td/telegram/RequestActor.h"
|
#include "td/telegram/RequestActor.h"
|
||||||
#include "td/telegram/SecretChatId.h"
|
#include "td/telegram/SecretChatId.h"
|
||||||
@ -3679,6 +3680,8 @@ void Td::dec_actor_refcnt() {
|
|||||||
LOG(DEBUG) << "MessagesManager was cleared " << timer;
|
LOG(DEBUG) << "MessagesManager was cleared " << timer;
|
||||||
notification_manager_.reset();
|
notification_manager_.reset();
|
||||||
LOG(DEBUG) << "NotificationManager was cleared " << timer;
|
LOG(DEBUG) << "NotificationManager was cleared " << timer;
|
||||||
|
poll_manager_.reset();
|
||||||
|
LOG(DEBUG) << "PollManager was cleared " << timer;
|
||||||
stickers_manager_.reset();
|
stickers_manager_.reset();
|
||||||
LOG(DEBUG) << "StickersManager was cleared " << timer;
|
LOG(DEBUG) << "StickersManager was cleared " << timer;
|
||||||
updates_manager_.reset();
|
updates_manager_.reset();
|
||||||
@ -3854,6 +3857,8 @@ void Td::clear() {
|
|||||||
LOG(DEBUG) << "MessagesManager actor was cleared " << timer;
|
LOG(DEBUG) << "MessagesManager actor was cleared " << timer;
|
||||||
notification_manager_actor_.reset();
|
notification_manager_actor_.reset();
|
||||||
LOG(DEBUG) << "NotificationManager actor was cleared " << timer;
|
LOG(DEBUG) << "NotificationManager actor was cleared " << timer;
|
||||||
|
poll_manager_actor_.reset();
|
||||||
|
LOG(DEBUG) << "PollManager actor was cleared " << timer;
|
||||||
stickers_manager_actor_.reset();
|
stickers_manager_actor_.reset();
|
||||||
LOG(DEBUG) << "StickersManager actor was cleared " << timer;
|
LOG(DEBUG) << "StickersManager actor was cleared " << timer;
|
||||||
updates_manager_actor_.reset();
|
updates_manager_actor_.reset();
|
||||||
@ -4182,6 +4187,8 @@ Status Td::init(DbKey key) {
|
|||||||
G()->set_messages_manager(messages_manager_actor_.get());
|
G()->set_messages_manager(messages_manager_actor_.get());
|
||||||
notification_manager_ = make_unique<NotificationManager>(this, create_reference());
|
notification_manager_ = make_unique<NotificationManager>(this, create_reference());
|
||||||
notification_manager_actor_ = register_actor("NotificationManager", notification_manager_.get());
|
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());
|
G()->set_notification_manager(notification_manager_actor_.get());
|
||||||
stickers_manager_ = make_unique<StickersManager>(this, create_reference());
|
stickers_manager_ = make_unique<StickersManager>(this, create_reference());
|
||||||
stickers_manager_actor_ = register_actor("StickersManager", stickers_manager_.get());
|
stickers_manager_actor_ = register_actor("StickersManager", stickers_manager_.get());
|
||||||
|
@ -54,6 +54,7 @@ class NetStatsManager;
|
|||||||
class NotificationManager;
|
class NotificationManager;
|
||||||
class PasswordManager;
|
class PasswordManager;
|
||||||
class PhoneNumberManager;
|
class PhoneNumberManager;
|
||||||
|
class PollManager;
|
||||||
class PrivacyManager;
|
class PrivacyManager;
|
||||||
class SecureManager;
|
class SecureManager;
|
||||||
class SecretChatsManager;
|
class SecretChatsManager;
|
||||||
@ -149,6 +150,8 @@ class Td final : public NetQueryCallback {
|
|||||||
ActorOwn<MessagesManager> messages_manager_actor_;
|
ActorOwn<MessagesManager> messages_manager_actor_;
|
||||||
unique_ptr<NotificationManager> notification_manager_;
|
unique_ptr<NotificationManager> notification_manager_;
|
||||||
ActorOwn<NotificationManager> notification_manager_actor_;
|
ActorOwn<NotificationManager> notification_manager_actor_;
|
||||||
|
unique_ptr<PollManager> poll_manager_;
|
||||||
|
ActorOwn<PollManager> poll_manager_actor_;
|
||||||
unique_ptr<StickersManager> stickers_manager_;
|
unique_ptr<StickersManager> stickers_manager_;
|
||||||
ActorOwn<StickersManager> stickers_manager_actor_;
|
ActorOwn<StickersManager> stickers_manager_actor_;
|
||||||
unique_ptr<UpdatesManager> updates_manager_;
|
unique_ptr<UpdatesManager> updates_manager_;
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "td/telegram/net/NetQuery.h"
|
#include "td/telegram/net/NetQuery.h"
|
||||||
#include "td/telegram/NotificationManager.h"
|
#include "td/telegram/NotificationManager.h"
|
||||||
#include "td/telegram/Payments.h"
|
#include "td/telegram/Payments.h"
|
||||||
|
#include "td/telegram/PollManager.h"
|
||||||
#include "td/telegram/PrivacyManager.h"
|
#include "td/telegram/PrivacyManager.h"
|
||||||
#include "td/telegram/SecretChatId.h"
|
#include "td/telegram/SecretChatId.h"
|
||||||
#include "td/telegram/SecretChatsManager.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_));
|
std::move(update->difference_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsupported updates
|
|
||||||
|
|
||||||
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, bool /*force_apply*/) {
|
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
|
} // namespace td
|
||||||
|
@ -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::updateLangPackTooLong> update, bool /*force_apply*/);
|
||||||
void on_update(tl_object_ptr<telegram_api::updateLangPack> 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*/);
|
void on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, bool /*force_apply*/);
|
||||||
|
|
||||||
|
// unsupported updates
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -21,10 +21,10 @@ class WebPageId {
|
|||||||
public:
|
public:
|
||||||
WebPageId() = default;
|
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>>
|
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 {
|
int64 get() const {
|
||||||
return id;
|
return id;
|
||||||
@ -54,13 +54,13 @@ class WebPageId {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct WebPageIdHash {
|
struct WebPageIdHash {
|
||||||
std::size_t operator()(WebPageId webpage_id) const {
|
std::size_t operator()(WebPageId web_page_id) const {
|
||||||
return std::hash<int64>()(webpage_id.get());
|
return std::hash<int64>()(web_page_id.get());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId webpage_id) {
|
inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId web_page_id) {
|
||||||
return string_builder << "web page " << webpage_id.get();
|
return string_builder << "web page " << web_page_id.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -2975,6 +2975,14 @@ class CliClient final : public Actor {
|
|||||||
|
|
||||||
send_message(chat_id, make_tl_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
|
send_message(chat_id, make_tl_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
|
||||||
to_integer<int32>(period)));
|
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") {
|
} else if (op == "sp") {
|
||||||
string chat_id;
|
string chat_id;
|
||||||
string photo_path;
|
string photo_path;
|
||||||
|
@ -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 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);
|
static string extract_file_reference(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo);
|
||||||
|
|
||||||
template <class T>
|
template <class StorerT>
|
||||||
void store_file(FileId file_id, T &storer, int32 ttl = 5) const;
|
void store_file(FileId file_id, StorerT &storer, int32 ttl = 5) const;
|
||||||
|
|
||||||
template <class T>
|
template <class StorerT>
|
||||||
FileId parse_file(T &parser);
|
FileId parse_file(StorerT &parser);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr char PERSISTENT_ID_VERSION = 2;
|
static constexpr char PERSISTENT_ID_VERSION = 2;
|
||||||
|
Loading…
Reference in New Issue
Block a user