From a7501e1582bf018926cc5f95279b383256393ae9 Mon Sep 17 00:00:00 2001 From: levlam Date: Sat, 11 Jan 2020 03:46:26 +0300 Subject: [PATCH] Update layer to 109. Add new poll types. GitOrigin-RevId: cc82f1bfdf1d4cd906212009f2dc8d84e0cb543a --- td/generate/scheme/td_api.tl | 64 +++++++++------ td/generate/scheme/td_api.tlo | Bin 166140 -> 166528 bytes td/generate/scheme/telegram_api.tl | 13 ++- td/generate/scheme/telegram_api.tlo | Bin 198616 -> 199724 bytes td/telegram/MessageContent.cpp | 39 ++++++++- td/telegram/MessageContent.h | 2 + td/telegram/MessagesManager.cpp | 22 ++--- td/telegram/PollManager.cpp | 121 ++++++++++++++++++++++++---- td/telegram/PollManager.h | 9 ++- td/telegram/PollManager.hpp | 51 +++++++++++- td/telegram/UpdatesManager.cpp | 13 +++ td/telegram/Version.h | 3 +- td/telegram/cli.cpp | 11 ++- 13 files changed, 281 insertions(+), 67 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index e9b52da72..3f57a27f8 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -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 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 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 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 = 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 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 = Ok; //@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index cf0a88ec93e4416c424bec24e6342acc16825c97..3e3f0c071874d866e1670f56d897abc008ed2ef8 100644 GIT binary patch delta 1053 zcmew}k*i@U7w@Cl`c@23@Mk0MbODxA>%hD0g~K_?R$`TD|YfAc$07J zlHde+@#ZlhALhvy->6Sk*bQ;|`{R$W}E!+t@z{%d(J?l7VL#6 zo~{tbs4)4>-Ux`W$37J>V{^ei5vZ*l`yoP;H|(DRRdfKR=s*fo(T#%|aGO{rfBYal z*V_YOp%y9}F@UHEIRX_i z_zV@<028@!1nPT>qfkdB99_U@2nr%ty0|LG6~mgJQsg&IG|P*Br2(vd+jp1cjJb`2ANrF!KUSGpm4RpqnR{oL^Lw znp_f}Ur>^npEq6Kicu0A*C2)4UpO(gRDr#|yfzuppvRRlqv>YsA7>E5)fM? zLd+vf&>VW`q5e=)HtA{`+ijYJ2p)29ab%{b{9OPcb>LMrsmxyWv)-*MHY`;^^Or!E20#$O}KHNoG;cv-q=zaVF#tO$oA| zGGX)`&{|QJ{dAH)8+}Iy9aFAJ)3Sg|RW%PJc62^q~wGZD+j5&%r+OpmJLk zh{iX;PF6WY5?SNL@%2W4Q_oVM#tKGR0ypNnV3G)aqbkPi38+h>-7tEGa^WdBq#cWZ zyH#5$Q-$nagW5hG{0w`f@xP#L9=R~%$bDz}=rQ7kl$Xx6F = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -1002,11 +1002,11 @@ help.userInfo#1eb3758 message:string entities:Vector author:strin pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; -poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector = 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 = 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 total_voters:flags.2?int = PollResults; +pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector = 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 settings:flags.1?ThemeSettings = WebPageAttribute; +messageUserVote#f212f56d user_id:int option:bytes = MessageUserVote; + +messages.votesList#823f649 flags:# count:int votes:Vector users:Vector 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 = messages.Messages; messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = 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; diff --git a/td/generate/scheme/telegram_api.tlo b/td/generate/scheme/telegram_api.tlo index fc4bdd73965b4f3ccf9838d7f4b043f813023f45..1cc0fca30bace4f0e66263ea4583cf6b127a6a38 100644 GIT binary patch delta 1015 zcmZWoT}V@57~c1FmaBHs#*rqDJmIkxtvrSo5=x7@s8FydNh@@%lP#t@?Q9lN5YZnc zwDm?`P*6l*VU%_*)(LeKCp3wHg_C3E&(KJu1vQ1Nh4S|pny>8d1gohk=LsFA^ zPtkPY!i_&mEw(lmrmdtwTCGnDMXwdAdxwUjKlZnn!(bLt7j z;PO+aK?-t>3?KVM=uD~%IP*+2en@&Hu7u394GeZABnFG8aV3K~6VjX+0uG{U0OZvc z=GxxpV$qoaBymo)f7Y!<24^cuHXLBPk~9{_}dK6Sls{33X54 zRdDx3hm$rsv$Nqvb9a^veCnR>YgfaeI%SW~Qy=jAO~z#9mle=BR4f!vjxEdg27*Dw z8*28nYX_B}2Jv$eDL96#5IM;vr5_vWv>7V&g7NfPW5Xm~f<(ee=#bsGhTNKGu7P5Z zKJFsbO-SOX6;mbDQ;95OaU%-wyc*u^_j#K;vPTgFq?8uqw}t&7pXyhdz54<_uLADP zqM17o_8lhg-KexH(ufcVeZworqb9V}l9y8ra&R%JX+p)9cAzSO-rhVl+vXNU%a+rq z4e59VIl2R#19zjD&vvS`hZwI0XZJ6G^r&ET6ccu$X^)wvn8>$_s3hikjGpdy{CmYz zuyMsEijxeFD#)%fTnyq>Ge@1!J6d8iOwvi@CL^D)K&mE@gQM}W4^Hf~qfIe>EpFax zj5MZp;WNl=td2y-FvKFaac!YEzpDK~;ory8s)XqFj5lO5sG;S&Ope^b)pW!b62FHX v*%6q@7c0(#f`~|dpTrU)O+Ut!iwrU%c4g(XjNz%6^&M_mkW=s#@3a2_UgeeH delta 663 zcmZ28gXcy+5AUPd`c@23Ah(hCHZP0xkNsAg-|(`qZ>9Xq_!N_fiM#63^t3LaAAb-Vot)eLJdA}(g7mF zatdnYg*OJ1*VwI?e87bRBExeU;=JhxQW-TSC!B_=Yk>++-f+4GqR`+B)K0j_gELUY z2QElVmN_c|QD<@%>WB|!2Ag}%DnQ+~;T%-hVY%UC&hzEEpum9z^Hn*nnCP<2Y0r@#O46s0DoP6=TG7oaFZg#oQBLuc^+Fd4t>1QS~>1;N5T;d3hoaqj7 zj0)3Fs4!M+-=WI5#AI^Hebwo*sf_H?G8tv3FO6bUpDrNG#4*9*Ax?ILxFW f#|jRI?F%0>F*9yYc*K+-2-ODk!uBVhnLH!_WE}^@ diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 412492b99..f043f68b2 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -1806,8 +1806,34 @@ static Result create_input_message_content( } } - content = make_unique( - 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(input_poll->type_); + allow_multiple_answers = type->allow_multiple_answers_; + break; + } + case td_api::pollTypeQuiz::ID: { + auto type = td_api::move_object_as(input_poll->type_); + is_quiz = true; + correct_option_id = type->correct_option_id_; + if (correct_option_id < 0 || correct_option_id >= static_cast(input_poll->options_.size())) { + return Status::Error(400, "Wrong correct option ID specified"); + } + break; + } + default: + UNREACHABLE(); + } + + content = make_unique(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(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(content)->web_page_id; diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index 9b08ddd60..dcc188262 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -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); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 8d778b753..0588cbd3c 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -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) { diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index e47bd6471..ecd89f1d8 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -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(); poll->flags_ |= telegram_api::poll::CLOSED_MASK; - auto input_media = telegram_api::make_object(std::move(poll)); + auto input_media = + telegram_api::make_object(0, std::move(poll), vector()); 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>(), 0))); @@ -473,15 +473,23 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co poll_options[i]->vote_percentage_ = vote_percentage[i]; } } + td_api::object_ptr poll_type; + if (poll->is_quiz) { + poll_type = td_api::make_object(poll->correct_option_id); + } else { + poll_type = td_api::make_object(poll->allow_multiple_answers); + } + return td_api::make_object(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 PollManager::get_input_poll_option(const PollOption &poll_option) { return telegram_api::make_object(poll_option.text, BufferSlice(poll_option.data)); } -PollId PollManager::create_poll(string &&question, vector &&options) { +PollId PollManager::create_poll(string &&question, vector &&options, bool is_anonymous, + bool allow_multiple_answers, bool is_quiz, int32 correct_option_id, bool is_closed) { auto poll = make_unique(); poll->question = std::move(question); int pos = '0'; @@ -491,6 +499,11 @@ PollId PollManager::create_poll(string &&question, vector &&options) { option.data = string(1, narrow_cast(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 &&option_ids, Promise &&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 options; for (auto &option_id : option_ids) { auto index = static_cast(option_id); if (index >= poll->options.size()) { - return promise.set_error(Status::Error(400, "Invalid option id specified")); + 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> 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 PollManager::get_input_media(PollId poll_id) const { auto poll = get_poll(poll_id); CHECK(poll != nullptr); - return telegram_api::make_object(telegram_api::make_object( - 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 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( + flags, + telegram_api::make_object(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::get_poll_options( @@ -917,6 +968,25 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptris_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_ptrresults_) { + 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_ptrflags_ & 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(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_ptrtotal_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(); diff --git a/td/telegram/PollManager.h b/td/telegram/PollManager.h index 3a8a00a47..d07aac9e7 100644 --- a/td/telegram/PollManager.h +++ b/td/telegram/PollManager.h @@ -41,7 +41,8 @@ class PollManager : public Actor { static bool is_local_poll_id(PollId poll_id); - PollId create_poll(string &&question, vector &&options); + PollId create_poll(string &&question, vector &&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 &&option_ids, @@ -93,6 +96,10 @@ class PollManager : public Actor { string question; vector 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 diff --git a/td/telegram/PollManager.hpp b/td/telegram/PollManager.hpp index 374629332..5d583e714 100644 --- a/td/telegram/PollManager.hpp +++ b/td/telegram/PollManager.hpp @@ -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 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 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(options.size())) { + parser.set_error("Wrong correct_option_id"); + } + } } template @@ -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 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 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(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(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); diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index c1a14d0a3..6b66c564b 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -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(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(message->media_.get()); if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) { diff --git a/td/telegram/Version.h b/td/telegram/Version.h index 0393bed0a..2854ee93b 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -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 }; diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 0627ba0cf..08296ce90 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -3194,14 +3194,21 @@ class CliClient final : public Actor { send_message(chat_id, td_api::make_object(as_location(latitude, longitude), to_integer(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(question, std::move(options))); + td_api::object_ptr poll_type; + if (op == "squiz") { + poll_type = td_api::make_object(narrow_cast(options.size() - 1)); + } else { + poll_type = td_api::make_object(op == "spollm"); + } + send_message(chat_id, td_api::make_object(question, std::move(options), true, + std::move(poll_type))); } else if (op == "sp" || op == "spcaption" || op == "spttl") { string chat_id; string photo_path;