Update layer to 109. Add new poll types.

GitOrigin-RevId: cc82f1bfdf1d4cd906212009f2dc8d84e0cb543a
This commit is contained in:
levlam 2020-01-11 03:46:26 +03:00
parent 0f35cb04fd
commit a7501e1582
13 changed files with 281 additions and 67 deletions

View File

@ -211,6 +211,15 @@ maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPo
pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption;
//@class PollType @description Describes the type of a poll
//@description A regular poll @allow_multiple_answers True, if multiple answer options can be chosen simultaneously
pollTypeRegular allow_multiple_answers:Bool = PollType;
//@description A quiz, which has exactly one correct answer option and can be answered only once @correct_option_id 0-based identifier of the correct answer option
pollTypeQuiz correct_option_id:int32 = PollType;
//@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"
//@minithumbnail Animation minithumbnail; may be null @thumbnail Animation thumbnail; may be null @animation File containing the animation
@ -257,8 +266,10 @@ 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 @id Unique poll identifier @question Poll question, 1-255 characters @options List of poll answer options @total_voter_count Total number of voters, participating in the poll @is_closed True, if the poll is closed
poll id:int64 question:string options:vector<pollOption> total_voter_count:int32 is_closed:Bool = Poll;
//@description Describes a poll @id Unique poll identifier @question Poll question, 1-255 characters @options List of poll answer options
//@total_voter_count Total number of voters, participating in the poll @is_closed True, if the poll is closed
//@is_anonymous True, if the poll is anonymous @type Type of the poll
poll id:int64 question:string options:vector<pollOption> total_voter_count:int32 is_closed:Bool is_anonymous:Bool type:PollType = 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
@ -269,7 +280,7 @@ profilePhoto id:int64 small:file big:file = ProfilePhoto;
chatPhoto small:file big:file = ChatPhoto;
//@class UserType @description Represents the type of the user. The following types are possible: regular users, deleted users and bots
//@class UserType @description Represents the type of a user. The following types are possible: regular users, deleted users and bots
//@description A regular user
userTypeRegular = UserType;
@ -972,9 +983,13 @@ pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageB
webPageInstantView page_blocks:vector<PageBlock> version:int32 url:string is_rtl:Bool is_full:Bool = WebPageInstantView;
//@description Describes a web page preview @url Original URL of the link @display_url URL to display
//@description Describes a web page preview
//@url Original URL of the link
//@display_url URL to display
//@type Type of the web page. Can be: article, photo, audio, video, document, profile, app, or something else
//@site_name Short name of the site (e.g., Google Docs, App Store) @title Title of the content @param_description Description of the content
//@site_name Short name of the site (e.g., Google Docs, App Store)
//@title Title of the content
//@param_description Description of the content
//@photo Image representing the content; may be null
//@embed_url URL to show in the embedded preview
//@embed_type MIME type of the embedded preview, (e.g., text/html or video/mp4)
@ -1302,50 +1317,50 @@ inputPassportElementError type:PassportElementType message:string source:InputPa
//@description A text message @text Text of the message @web_page A preview of the web page that's mentioned in the text; may be null
messageText text:formattedText web_page:webPage = MessageContent;
//@description An animation message (GIF-style). @animation Message content @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
//@description An animation message (GIF-style). @animation The animation description @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
messageAnimation animation:animation caption:formattedText is_secret:Bool = MessageContent;
//@description An audio message @audio Message content @caption Audio caption
//@description An audio message @audio The audio description @caption Audio caption
messageAudio audio:audio caption:formattedText = MessageContent;
//@description A document message (general file) @document Message content @caption Document caption
//@description A document message (general file) @document The document description @caption Document caption
messageDocument document:document caption:formattedText = MessageContent;
//@description A photo message @photo Message content @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
//@description A photo message @photo The photo description @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
messagePhoto photo:photo caption:formattedText is_secret:Bool = MessageContent;
//@description An expired photo message (self-destructed after TTL has elapsed)
messageExpiredPhoto = MessageContent;
//@description A sticker message @sticker Message content
//@description A sticker message @sticker The sticker description
messageSticker sticker:sticker = MessageContent;
//@description A video message @video Message content @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
//@description A video message @video The video description @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
messageVideo video:video caption:formattedText is_secret:Bool = MessageContent;
//@description An expired video message (self-destructed after TTL has elapsed)
messageExpiredVideo = MessageContent;
//@description A video note message @video_note Message content @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
//@description A video note message @video_note The video note description @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
messageVideoNote video_note:videoNote is_viewed:Bool is_secret:Bool = MessageContent;
//@description A voice note message @voice_note Message content @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
//@description A voice note message @voice_note The voice note description @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
messageVoiceNote voice_note:voiceNote caption:formattedText is_listened:Bool = MessageContent;
//@description A message with a location @location Message content @live_period Time relative to the message sent date until which the location can be updated, in seconds
//@description A message with a location @location The location description @live_period Time relative to the message sent date until which the location can be updated, in seconds
//@expires_in Left time for which the location can be updated, in seconds. updateMessageContent is not sent when this field changes
messageLocation location:location live_period:int32 expires_in:int32 = MessageContent;
//@description A message with information about a venue @venue Message content
//@description A message with information about a venue @venue The venue description
messageVenue venue:venue = MessageContent;
//@description A message with a user contact @contact Message content
//@description A message with a user contact @contact The contact description
messageContact contact:contact = MessageContent;
//@description A message with a game @game Game
//@description A message with a game @game The game description
messageGame game:game = MessageContent;
//@description A message with a poll @poll Poll
//@description A message with a poll @poll The poll description
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
@ -1548,7 +1563,8 @@ inputMessageGame bot_user_id:int32 game_short_name: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. Polls can't be sent to private or secret chats @question Poll question, 1-255 characters @options List of poll answer options, 2-10 strings 1-100 characters each
inputMessagePoll question:string options:vector<string> = InputMessageContent;
//@is_anonymous True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels @type Type of the poll
inputMessagePoll question:string options:vector<string> is_anonymous:Bool type:PollType = 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
@ -2106,7 +2122,7 @@ backgroundFillSolid color:int32 = BackgroundFill;
backgroundFillGradient top_color:int32 bottom_color:int32 rotation_angle:int32 = BackgroundFill;
//@class BackgroundType @description Describes a type of a background
//@class BackgroundType @description Describes the type of a background
//@description A wallpaper in JPEG format
//@is_blurred True, if the wallpaper must be downscaled to fit in 450x450 square and then box-blurred with radius 12
@ -2283,7 +2299,7 @@ notificationTypeNewCall call_id:int32 = NotificationType;
notificationTypeNewPushMessage message_id:int53 sender_user_id:int32 content:PushMessageContent = NotificationType;
//@class NotificationGroupType @description Describes type of notifications in the group
//@class NotificationGroupType @description Describes the type of notifications in a notification group
//@description A group containing notifications of type notificationTypeNewMessage and notificationTypeNewPushMessage with ordinary unread messages
notificationGroupTypeMessages = NotificationGroupType;
@ -2683,7 +2699,7 @@ textParseModeMarkdown version:int32 = TextParseMode;
textParseModeHTML = TextParseMode;
//@class ProxyType @description Describes the type of the proxy server
//@class ProxyType @description Describes the type of a proxy server
//@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty
proxyTypeSocks5 username:string password:string = ProxyType;
@ -3411,8 +3427,8 @@ getJsonValue json:string = JsonValue;
getJsonString json_value:JsonValue = Text;
//@description Changes user answer to a poll @chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_ids 0-based identifiers of options, chosen by the user. Currently user can't choose more than 1 option
//@description Changes the user answer to a poll. A quiz poll can be answered only once @chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_ids 0-based identifiers of options, chosen by the user. User can choose more than 1 option only is the poll allows multiple answers
setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set

Binary file not shown.

View File

@ -58,7 +58,7 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
inputMediaPoll#6b3765b poll:Poll = InputMedia;
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@ -1002,11 +1002,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults;
chatOnlines#f041e250 onlines:int = ChatOnlines;
@ -1093,6 +1093,10 @@ themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
messageUserVote#f212f56d user_id:int option:bytes = MessageUserVote;
messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1329,6 +1333,7 @@ messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Message
messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;

Binary file not shown.

View File

@ -1806,8 +1806,34 @@ static Result<InputMessageContent> create_input_message_content(
}
}
content = make_unique<MessagePoll>(
td->poll_manager_->create_poll(std::move(input_poll->question_), std::move(input_poll->options_)));
bool allow_multiple_answers = false;
bool is_quiz = false;
int32 correct_option_id = -1;
if (input_poll->type_ == nullptr) {
return Status::Error(400, "Poll type must not be empty");
}
switch (input_poll->type_->get_id()) {
case td_api::pollTypeRegular::ID: {
auto type = td_api::move_object_as<td_api::pollTypeRegular>(input_poll->type_);
allow_multiple_answers = type->allow_multiple_answers_;
break;
}
case td_api::pollTypeQuiz::ID: {
auto type = td_api::move_object_as<td_api::pollTypeQuiz>(input_poll->type_);
is_quiz = true;
correct_option_id = type->correct_option_id_;
if (correct_option_id < 0 || correct_option_id >= static_cast<int32>(input_poll->options_.size())) {
return Status::Error(400, "Wrong correct option ID specified");
}
break;
}
default:
UNREACHABLE();
}
content = make_unique<MessagePoll>(td->poll_manager_->create_poll(
std::move(input_poll->question_), std::move(input_poll->options_), input_poll->is_anonymous_,
allow_multiple_answers, is_quiz, correct_option_id, false));
break;
}
default:
@ -2727,6 +2753,15 @@ bool get_message_content_poll_is_closed(const Td *td, const MessageContent *cont
}
}
bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Poll:
return td->poll_manager_->get_poll_is_anonymous(static_cast<const MessagePoll *>(content)->poll_id);
default:
return true;
}
}
WebPageId get_message_content_web_page_id(const MessageContent *content) {
if (content->get_type() == MessageContentType::Text) {
return static_cast<const MessageText *>(content)->web_page_id;

View File

@ -184,6 +184,8 @@ int32 get_message_content_live_location_period(const MessageContent *content);
bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content);
bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content);
WebPageId get_message_content_web_page_id(const MessageContent *content);
void set_message_content_web_page_id(MessageContent *content, WebPageId web_page_id);

View File

@ -17837,23 +17837,8 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
}
break;
case MessageContentType::Game:
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
// ok
break;
case DialogType::Channel: {
auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
if (channel_type == ChannelType::Broadcast) {
// return Status::Error(400, "Games can't be sent to channel chats");
}
break;
}
case DialogType::SecretChat:
return Status::Error(400, "Games can't be sent to secret chats");
case DialogType::None:
default:
UNREACHABLE();
if (is_broadcast_channel(dialog_id)) {
// return Status::Error(400, "Games can't be sent to channel chats");
}
if (!can_send_games) {
@ -17898,6 +17883,9 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
if (!can_send_polls) {
return Status::Error(400, "Not enough rights to send polls to the chat");
}
if (!get_message_content_poll_is_anonymous(td_, content) && is_broadcast_channel(dialog_id)) {
return Status::Error(400, "Non-anonymous polls can't be sent to channel chats");
}
break;
case MessageContentType::Sticker:
if (!can_send_stickers) {

View File

@ -57,8 +57,7 @@ class GetPollResultsQuery : public Td::ResultHandler {
auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
LOG(INFO) << "Can't reget poll, because have no read access to " << dialog_id_;
// do not signal error to PollManager
return;
return promise_.set_value(nullptr);
}
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
@ -151,7 +150,8 @@ class StopPollActor : public NetActorOnce {
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
auto poll = telegram_api::make_object<telegram_api::poll>();
poll->flags_ |= telegram_api::poll::CLOSED_MASK;
auto input_media = telegram_api::make_object<telegram_api::inputMediaPoll>(std::move(poll));
auto input_media =
telegram_api::make_object<telegram_api::inputMediaPoll>(0, std::move(poll), vector<BufferSlice>());
auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_editMessage(
flags, false /*ignored*/, std::move(input_peer), message_id, string(), std::move(input_media),
std::move(input_reply_markup), vector<tl_object_ptr<telegram_api::MessageEntity>>(), 0)));
@ -473,15 +473,23 @@ td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id, co
poll_options[i]->vote_percentage_ = vote_percentage[i];
}
}
td_api::object_ptr<td_api::PollType> poll_type;
if (poll->is_quiz) {
poll_type = td_api::make_object<td_api::pollTypeQuiz>(poll->correct_option_id);
} else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers);
}
return td_api::make_object<td_api::poll>(poll_id.get(), poll->question, std::move(poll_options), total_voter_count,
poll->is_closed);
poll->is_closed, poll->is_anonymous, std::move(poll_type));
}
telegram_api::object_ptr<telegram_api::pollAnswer> PollManager::get_input_poll_option(const PollOption &poll_option) {
return telegram_api::make_object<telegram_api::pollAnswer>(poll_option.text, BufferSlice(poll_option.data));
}
PollId PollManager::create_poll(string &&question, vector<string> &&options) {
PollId PollManager::create_poll(string &&question, vector<string> &&options, bool is_anonymous,
bool allow_multiple_answers, bool is_quiz, int32 correct_option_id, bool is_closed) {
auto poll = make_unique<Poll>();
poll->question = std::move(question);
int pos = '0';
@ -491,6 +499,11 @@ PollId PollManager::create_poll(string &&question, vector<string> &&options) {
option.data = string(1, narrow_cast<char>(pos++));
poll->options.push_back(std::move(option));
}
poll->is_anonymous = is_anonymous;
poll->allow_multiple_answers = allow_multiple_answers;
poll->is_quiz = is_quiz;
poll->correct_option_id = correct_option_id;
poll->is_closed = is_closed;
PollId poll_id(--current_local_poll_id_);
CHECK(is_local_poll_id(poll_id));
@ -536,6 +549,12 @@ bool PollManager::get_poll_is_closed(PollId poll_id) const {
return poll->is_closed;
}
bool PollManager::get_poll_is_anonymous(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return poll->is_anonymous;
}
string PollManager::get_poll_search_text(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
@ -550,11 +569,11 @@ string PollManager::get_poll_search_text(PollId poll_id) const {
void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
Promise<Unit> &&promise) {
if (option_ids.size() > 1) {
return promise.set_error(Status::Error(400, "Can't choose more than 1 option"));
}
std::sort(option_ids.begin(), option_ids.end());
option_ids.erase(std::unique(option_ids.begin(), option_ids.end()), option_ids.end());
if (is_local_poll_id(poll_id)) {
return promise.set_error(Status::Error(5, "Poll can't be answered"));
return promise.set_error(Status::Error(400, "Poll can't be answered"));
}
auto poll = get_poll(poll_id);
@ -562,11 +581,18 @@ void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id,
if (poll->is_closed) {
return promise.set_error(Status::Error(400, "Can't answer closed poll"));
}
if (!poll->allow_multiple_answers && option_ids.size() > 1) {
return promise.set_error(Status::Error(400, "Can't choose more than 1 option in the poll"));
}
if (poll->is_quiz && option_ids.empty()) {
return promise.set_error(Status::Error(400, "Can't retract vote in a quiz"));
}
vector<string> options;
for (auto &option_id : option_ids) {
auto index = static_cast<size_t>(option_id);
if (index >= poll->options.size()) {
return promise.set_error(Status::Error(400, "Invalid option id specified"));
return promise.set_error(Status::Error(400, "Invalid option ID specified"));
}
options.push_back(poll->options[index].data);
}
@ -809,16 +835,19 @@ void PollManager::on_update_poll_timeout(PollId poll_id) {
void PollManager::on_get_poll_results(PollId poll_id, uint64 generation,
Result<tl_object_ptr<telegram_api::Updates>> result) {
if (result.is_error()) {
if (!get_poll_is_closed(poll_id) && !td_->auth_manager_->is_bot()) {
if (!get_poll_is_closed(poll_id) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
auto timeout = get_polling_timeout();
LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
update_poll_timeout_.add_timeout_in(poll_id.get(), timeout);
}
return;
}
if (result.ok() == nullptr) {
return;
}
if (generation != current_generation_) {
LOG(INFO) << "Receive possibly outdated result of " << poll_id << ", reget it";
if (!get_poll_is_closed(poll_id) && !td_->auth_manager_->is_bot()) {
if (!get_poll_is_closed(poll_id) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
}
return;
@ -845,8 +874,30 @@ void PollManager::on_online() {
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->options, get_input_poll_option)));
int32 poll_flags = 0;
if (!poll->is_anonymous) {
poll_flags |= telegram_api::poll::PUBLIC_VOTERS_MASK;
}
if (poll->allow_multiple_answers) {
poll_flags |= telegram_api::poll::MULTIPLE_CHOICE_MASK;
}
if (poll->is_quiz) {
poll_flags |= telegram_api::poll::QUIZ_MASK;
}
int32 flags = 0;
vector<BufferSlice> correct_answers;
if (poll->is_quiz) {
flags |= telegram_api::inputMediaPoll::CORRECT_ANSWERS_MASK;
correct_answers.push_back(BufferSlice(poll->options[poll->correct_option_id].data));
}
return telegram_api::make_object<telegram_api::inputMediaPoll>(
flags,
telegram_api::make_object<telegram_api::poll>(0, flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, poll->question,
transform(poll->options, get_input_poll_option)),
std::move(correct_answers));
}
vector<PollManager::PollOption> PollManager::get_poll_options(
@ -917,6 +968,25 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
poll->is_closed = is_closed;
is_changed = true;
}
bool is_anonymous = (poll_server->flags_ & telegram_api::poll::PUBLIC_VOTERS_MASK) == 0;
if (is_anonymous != poll->is_anonymous) {
poll->is_anonymous = is_anonymous;
is_changed = true;
}
bool allow_multiple_answers = (poll_server->flags_ & telegram_api::poll::MULTIPLE_CHOICE_MASK) != 0;
bool is_quiz = (poll_server->flags_ & telegram_api::poll::QUIZ_MASK) != 0;
if (is_quiz && allow_multiple_answers) {
LOG(ERROR) << "Receive quiz " << poll_id << " allowing multiple answers";
allow_multiple_answers = false;
}
if (allow_multiple_answers != poll->allow_multiple_answers) {
poll->allow_multiple_answers = allow_multiple_answers;
is_changed = true;
}
if (is_quiz != poll->is_quiz) {
poll->is_quiz = is_quiz;
is_changed = true;
}
}
CHECK(poll_results != nullptr);
@ -930,7 +1000,9 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
}
is_changed = true;
}
for (auto &poll_result : poll_results->results_) {
int32 correct_option_id = -1;
for (size_t i = 0; i < poll_results->results_.size(); i++) {
auto &poll_result = poll_results->results_[i];
Slice data = poll_result->option_.as_slice();
for (auto &option : poll->options) {
if (option.data != data) {
@ -943,6 +1015,13 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
is_changed = true;
}
}
bool is_correct = (poll_result->flags_ & telegram_api::pollAnswerVoters::CORRECT_MASK) != 0;
if (is_correct) {
if (correct_option_id != -1) {
LOG(ERROR) << "Receive more than 1 correct answers " << correct_option_id << " and " << i;
}
correct_option_id = static_cast<int32>(i);
}
if (poll_result->voters_ != option.voter_count) {
option.voter_count = poll_result->voters_;
if (option.voter_count < 0) {
@ -978,6 +1057,18 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
poll->total_voter_count = max_total_voter_count;
}
}
if (poll->is_quiz) {
if (correct_option_id == -1) {
LOG(ERROR) << "Have no correct option in quiz " << poll_id;
correct_option_id = 0;
}
if (poll->correct_option_id != correct_option_id) {
poll->correct_option_id = correct_option_id;
is_changed = true;
}
} else if (correct_option_id != -1) {
LOG(ERROR) << "Receive correct option " << correct_option_id << " in quiz " << poll_id;
}
if (!td_->auth_manager_->is_bot() && !poll->is_closed) {
auto timeout = get_polling_timeout();

View File

@ -41,7 +41,8 @@ class PollManager : public Actor {
static bool is_local_poll_id(PollId poll_id);
PollId create_poll(string &&question, vector<string> &&options);
PollId create_poll(string &&question, vector<string> &&options, bool is_anonymous, bool allow_multiple_answers,
bool is_quiz, int32 correct_option_id, bool is_closed);
void register_poll(PollId poll_id, FullMessageId full_message_id);
@ -49,6 +50,8 @@ class PollManager : public Actor {
bool get_poll_is_closed(PollId poll_id) const;
bool get_poll_is_anonymous(PollId poll_id) const;
string get_poll_search_text(PollId poll_id) const;
void set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
@ -93,6 +96,10 @@ class PollManager : public Actor {
string question;
vector<PollOption> options;
int32 total_voter_count = 0;
int32 correct_option_id = -1;
bool is_anonymous = true;
bool allow_multiple_answers = false;
bool is_quiz = false;
bool is_closed = false;
template <class StorerT>

View File

@ -7,6 +7,7 @@
#pragma once
#include "td/telegram/PollManager.h"
#include "td/telegram/Version.h"
#include "td/utils/common.h"
#include "td/utils/misc.h"
@ -41,25 +42,43 @@ void PollManager::PollOption::parse(ParserT &parser) {
template <class StorerT>
void PollManager::Poll::store(StorerT &storer) const {
using ::td::store;
bool is_public = !is_anonymous;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_closed);
STORE_FLAG(is_public);
STORE_FLAG(allow_multiple_answers);
STORE_FLAG(is_quiz);
END_STORE_FLAGS();
store(question, storer);
store(options, storer);
store(total_voter_count, storer);
if (is_quiz) {
store(correct_option_id, storer);
}
}
template <class ParserT>
void PollManager::Poll::parse(ParserT &parser) {
using ::td::parse;
bool is_public;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_closed);
PARSE_FLAG(is_public);
PARSE_FLAG(allow_multiple_answers);
PARSE_FLAG(is_quiz);
END_PARSE_FLAGS();
is_anonymous = !is_public;
parse(question, parser);
parse(options, parser);
parse(total_voter_count, parser);
if (is_quiz) {
parse(correct_option_id, parser);
if (correct_option_id < 0 || correct_option_id >= static_cast<int32>(options.size())) {
parser.set_error("Wrong correct_option_id");
}
}
}
template <class StorerT>
@ -68,9 +87,18 @@ void PollManager::store_poll(PollId poll_id, StorerT &storer) const {
if (is_local_poll_id(poll_id)) {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
BEGIN_STORE_FLAGS();
STORE_FLAG(poll->is_closed);
STORE_FLAG(poll->is_anonymous);
STORE_FLAG(poll->allow_multiple_answers);
STORE_FLAG(poll->is_quiz);
END_STORE_FLAGS();
store(poll->question, storer);
vector<string> options = transform(poll->options, [](const PollOption &option) { return option.text; });
store(options, storer);
if (poll->is_quiz) {
store(poll->correct_option_id, storer);
}
}
}
@ -82,12 +110,33 @@ PollId PollManager::parse_poll(ParserT &parser) {
if (is_local_poll_id(poll_id)) {
string question;
vector<string> options;
bool is_closed = false;
bool is_anonymous = true;
bool allow_multiple_answers = false;
bool is_quiz = false;
int32 correct_option_id = -1;
if (parser.version() >= static_cast<int32>(Version::SupportPolls2_0)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_closed);
PARSE_FLAG(is_anonymous);
PARSE_FLAG(allow_multiple_answers);
PARSE_FLAG(is_quiz);
END_PARSE_FLAGS();
}
parse(question, parser);
parse(options, parser);
if (is_quiz) {
parse(correct_option_id, parser);
if (correct_option_id < 0 || correct_option_id >= static_cast<int32>(options.size())) {
parser.set_error("Wrong correct_option_id");
}
}
if (parser.get_error() != nullptr) {
return PollId();
}
return create_poll(std::move(question), std::move(options));
return create_poll(std::move(question), std::move(options), is_anonymous, allow_multiple_answers, is_quiz,
correct_option_id, is_closed);
}
auto poll = get_poll_force(poll_id);

View File

@ -454,6 +454,19 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
}
}
/*
// the users are always min, so no need to check
if (media_id == telegram_api::messageMediaPoll::ID) {
auto message_media_poll = static_cast<const telegram_api::messageMediaPoll *>(message->media_.get());
for (auto recent_voter_user_id : message_media_poll->results_->recent_voters_) {
UserId user_id(recent_voter_user_id);
if (!is_acceptable_user(user_id)) {
return false;
}
}
}
*/
/*
// the channel is always min, so no need to check
if (media_id == telegram_api::messageMediaWebPage::ID) {
auto message_media_web_page = static_cast<const telegram_api::messageMediaWebPage *>(message->media_.get());
if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) {

View File

@ -8,7 +8,7 @@
namespace td {
constexpr int32 MTPROTO_LAYER = 108;
constexpr int32 MTPROTO_LAYER = 109;
enum class Version : int32 {
Initial,
@ -35,6 +35,7 @@ enum class Version : int32 {
AddVideoCallsSupport,
AddPhotoSizeSource,
AddFolders,
SupportPolls2_0,
Next
};

View File

@ -3194,14 +3194,21 @@ class CliClient final : public Actor {
send_message(chat_id, td_api::make_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
to_integer<int32>(period)));
} else if (op == "spoll") {
} else if (op == "spoll" || op == "spollm" || op == "squiz") {
string chat_id;
string question;
std::tie(chat_id, args) = split(args);
std::tie(question, args) = split(args);
auto options = full_split(args);
send_message(chat_id, td_api::make_object<td_api::inputMessagePoll>(question, std::move(options)));
td_api::object_ptr<td_api::PollType> poll_type;
if (op == "squiz") {
poll_type = td_api::make_object<td_api::pollTypeQuiz>(narrow_cast<int32>(options.size() - 1));
} else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(op == "spollm");
}
send_message(chat_id, td_api::make_object<td_api::inputMessagePoll>(question, std::move(options), true,
std::move(poll_type)));
} else if (op == "sp" || op == "spcaption" || op == "spttl") {
string chat_id;
string photo_path;