diff --git a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml index 42967044f4d..80b684933e8 100644 --- a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml +++ b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml @@ -2,4 +2,5 @@ features = "Full Support for Bot API 9.6" pull_requests = [ { uid = "5196", author_uid = "harshil21" }, + { uid = "5202", author_uid = "ouyooung" }, ] diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index fad336e0549..153be2d2682 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -229,6 +229,8 @@ "Poll", "PollAnswer", "PollOption", + "PollOptionAdded", + "PollOptionDeleted", "PreCheckoutQuery", "PreparedInlineMessage", "ProximityAlertTriggered", @@ -564,7 +566,14 @@ RevenueWithdrawalStateSucceeded, ) from ._payment.successfulpayment import SuccessfulPayment -from ._poll import InputPollOption, Poll, PollAnswer, PollOption +from ._poll import ( + InputPollOption, + Poll, + PollAnswer, + PollOption, + PollOptionAdded, + PollOptionDeleted, +) from ._proximityalerttriggered import ProximityAlertTriggered from ._reaction import ( ReactionCount, diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index 1050f05baf7..cfa3a765b24 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -102,6 +102,7 @@ from telegram._utils.types import ( BaseUrl, CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -7596,9 +7597,17 @@ async def send_poll( options: Sequence["str | InputPollOption"], is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin - allows_multiple_answers: bool | None = None, correct_option_id: CorrectOptionID | None = None, + allows_multiple_answers: bool | None = None, + allows_revoting: bool | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, is_closed: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -7652,6 +7661,25 @@ async def send_poll( multiple answers, ignored for polls in quiz mode, defaults to :obj:`False`. correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option, required for polls in quiz mode. + allows_revoting (:obj:`bool`, optional): :obj:`True`, if the poll allows to + change the chosen answer options, defaults to :obj:`False` for quizzes and to :obj:`True` for regular polls + + .. versionadded:: NEXT.VERSION + allow_adding_options (:obj:`bool`, optional): :obj:`True`, if answer options can be + added to the poll after creation; not supported for anonymous polls and quizzes + + .. versionadded:: NEXT.VERSION + shuffle_options (:obj:`bool`, optional): :obj:`True`, if the poll options must be + shown in random order + + .. versionadded:: NEXT.VERSION + hide_results_until_closes (:obj:`bool`, optional): :obj:`True`, if poll results + must be shown only after the poll closes + + .. versionadded:: NEXT.VERSION + correct_option_ids (Sequence[:class:`int`], optional): A list of monotonically increasing 0-based identifiers of the correct answer options, required for polls in quiz mode. + + .. versionadded:: NEXT.VERSION explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters with at most @@ -7682,6 +7710,21 @@ async def send_poll( |tz-naive-dtms| is_closed (:obj:`bool`, optional): Pass :obj:`True`, if the poll needs to be immediately closed. This can be useful for poll preview. + description (:obj:`str`, optional): Description of the poll to be sent, + 0-:tg-const:`telegram.Poll.MAX_DESCRIPTION_CHARACTERS` characters + after entities parsing. + + .. versionadded:: NEXT.VERSION + description_parse_mode (:obj:`str`, optional): Mode for parsing entities + in the poll description. See the constants + in :class:`telegram.constants.ParseMode` + + .. versionadded:: NEXT.VERSION + description_entities (Sequence[:class:`telegram.MessageEntity`], optional): A + JSON-serialized list of special entities that appear in the poll description, + which can be specified instead of :paramref:`description_parse_mode` + + .. versionadded:: NEXT.VERSION disable_notification (:obj:`bool`, optional): |disable_notification| protect_content (:obj:`bool`, optional): |protect_content| @@ -7756,7 +7799,15 @@ async def send_poll( "type": type, "allows_multiple_answers": allows_multiple_answers, "correct_option_id": correct_option_id, + "allow_adding_options": allow_adding_options, + "allows_revoting": allows_revoting, + "shuffle_options": shuffle_options, + "hide_results_until_closes": hide_results_until_closes, + "correct_option_ids": correct_option_ids, "is_closed": is_closed, + "description": description, + "description_parse_mode": description_parse_mode, + "description_entities": description_entities, "explanation": explanation, "explanation_entities": explanation_entities, "open_period": open_period, diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 1230bf48277..15a9dcf742e 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -34,6 +34,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -2296,9 +2297,17 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, - reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, connect_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2323,6 +2332,9 @@ async def send_poll( type=type, # pylint=pylint, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2346,6 +2358,11 @@ async def send_poll( business_connection_id=business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_copy( diff --git a/src/telegram/_message.py b/src/telegram/_message.py index c0d018ec406..4b1b81c677e 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -65,7 +65,7 @@ from telegram._payment.invoice import Invoice from telegram._payment.refundedpayment import RefundedPayment from telegram._payment.successfulpayment import SuccessfulPayment -from telegram._poll import Poll +from telegram._poll import Poll, PollOptionAdded, PollOptionDeleted from telegram._proximityalerttriggered import ProximityAlertTriggered from telegram._reply import ReplyParameters from telegram._shared import ChatShared, UsersShared @@ -80,6 +80,7 @@ from telegram._utils.strings import TextEncoding from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, JSONDict, MarkdownVersion, ODVInput, @@ -689,6 +690,18 @@ class Message(MaybeInaccessibleMessage): supergroups only .. versionadded:: 22.7 + poll_option_added (:class:`telegram.PollOptionAdded`, optional): Service message: + answer option was added to a poll. + + .. versionadded:: NEXT.VERSION + poll_option_deleted (:class:`telegram.PollOptionDeleted`, optional): Service message: + answer option was deleted from a poll. + + .. versionadded:: NEXT.VERSION + reply_to_poll_option_id (:obj:`str`, optional): Persistent + identifier of the specific poll option that is being replied to. + + .. versionadded:: NEXT.VERSION Attributes: message_id (:obj:`int`): Unique message identifier inside this chat. In specific instances @@ -1105,6 +1118,18 @@ class Message(MaybeInaccessibleMessage): supergroups only .. versionadded:: 22.7 + poll_option_added (:class:`telegram.PollOptionAdded`): Optional. Service message: + answer option was added to a poll. + + .. versionadded:: NEXT.VERSION + poll_option_deleted (:class:`telegram.PollOptionDeleted`): Optional. Service message: + answer option was deleted from a poll. + + .. versionadded:: NEXT.VERSION + reply_to_poll_option_id (:obj:`str`): Optional. Persistent + identifier of the specific poll option that is being replied to. + + .. versionadded:: NEXT.VERSION .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a @@ -1191,12 +1216,15 @@ class Message(MaybeInaccessibleMessage): "photo", "pinned_message", "poll", + "poll_option_added", + "poll_option_deleted", "proximity_alert_triggered", "quote", "refunded_payment", "reply_markup", "reply_to_checklist_task_id", "reply_to_message", + "reply_to_poll_option_id", "reply_to_story", "sender_boost_count", "sender_business_bot", @@ -1315,6 +1343,8 @@ def __init__( effect_id: str | None = None, show_caption_above_media: bool | None = None, paid_media: PaidMediaInfo | None = None, + poll_option_added: PollOptionAdded | None = None, + poll_option_deleted: PollOptionDeleted | None = None, refunded_payment: RefundedPayment | None = None, gift: GiftInfo | None = None, unique_gift: UniqueGiftInfo | None = None, @@ -1337,6 +1367,7 @@ def __init__( chat_owner_changed: ChatOwnerChanged | None = None, chat_owner_left: ChatOwnerLeft | None = None, sender_tag: str | None = None, + reply_to_poll_option_id: str | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -1467,6 +1498,9 @@ def __init__( self.chat_owner_changed: ChatOwnerChanged | None = chat_owner_changed self.chat_owner_left: ChatOwnerLeft | None = chat_owner_left self.sender_tag: str | None = sender_tag + self.poll_option_added: PollOptionAdded | None = poll_option_added + self.poll_option_deleted: PollOptionDeleted | None = poll_option_deleted + self.reply_to_poll_option_id: str | None = reply_to_poll_option_id self._effective_attachment = DEFAULT_NONE @@ -3464,24 +3498,32 @@ async def reply_poll( explanation: str | None = None, explanation_parse_mode: ODVInput[str] = DEFAULT_NONE, open_period: TimePeriod | None = None, - close_date: int | (dtm.datetime | None) = None, + close_date: int | dtm.datetime | None = None, explanation_entities: Sequence["MessageEntity"] | None = None, protect_content: ODVInput[bool] = DEFAULT_NONE, - message_thread_id: ODVInput[int] = DEFAULT_NONE, + message_thread_id: int | None = None, reply_parameters: "ReplyParameters | None" = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, - reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - do_quote: bool | (_ReplyKwargs | None) = None, + reply_to_message_id: int | None = None, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, connect_timeout: ODVInput[float] = DEFAULT_NONE, pool_timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict | None = None, + do_quote: bool | (_ReplyKwargs | None) = None, ) -> "Message": """Shortcut for:: @@ -3522,6 +3564,9 @@ async def reply_poll( type=type, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_parameters=effective_reply_parameters, @@ -3543,6 +3588,11 @@ async def reply_poll( question_entities=question_entities, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def reply_dice( diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 744edd22eff..20f77b3cea9 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -24,6 +24,7 @@ from telegram import constants from telegram._chat import Chat +from telegram._message import MaybeInaccessibleMessage from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject from telegram._user import User @@ -118,10 +119,26 @@ class PollOption(TelegramObject): considered equal, if their :attr:`text` and :attr:`voter_count` are equal. Args: + persistent_id (:obj:`str`): Unique + identifier of the option, persistent on option addition and deletion. + + .. versionadded:: NEXT.VERSION text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. voter_count (:obj:`int`): Number of users that voted for this option. + added_by_user (:class:`telegram.User`, optional): User who added the option; + omitted if the option wasn't added by a user after poll creation. + + .. versionadded:: NEXT.VERSION + added_by_chat (:class:`telegram.Chat`, optional): Chat that added the option; + omitted if the option wasn't added by a chat after poll creation. + + .. versionadded:: NEXT.VERSION + addition_date (:obj:`datetime.datetime`, optional): Point in time + when the option was added; omitted if the option existed in the original poll. + + .. versionadded:: NEXT.VERSION text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities that appear in the option text. Currently, only custom emoji entities are allowed in poll option texts. @@ -129,10 +146,26 @@ class PollOption(TelegramObject): .. versionadded:: 21.2 Attributes: + persistent_id (:obj:`str`): Unique + identifier of the option, persistent on option addition and deletion. + + .. versionadded:: NEXT.VERSION text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. voter_count (:obj:`int`): Number of users that voted for this option. + added_by_user (:class:`telegram.User`): Optional. User who added the option; + omitted if the option wasn't added by a user after poll creation. + + .. versionadded:: NEXT.VERSION + added_by_chat (:class:`telegram.Chat`): Optional. Chat that added the option; + omitted if the option wasn't added by a chat after poll creation. + + .. versionadded:: NEXT.VERSION + addition_date (:obj:`datetime.datetime`): Optional. Point in time + when the option was added; omitted if the option existed in the original poll. + + .. versionadded:: NEXT.VERSION text_entities (tuple[:class:`telegram.MessageEntity`]): Special entities that appear in the option text. Currently, only custom emoji entities are allowed in poll option texts. @@ -142,19 +175,35 @@ class PollOption(TelegramObject): """ - __slots__ = ("text", "text_entities", "voter_count") + __slots__ = ( + "added_by_chat", + "added_by_user", + "addition_date", + "persistent_id", + "text", + "text_entities", + "voter_count", + ) def __init__( self, text: str, voter_count: int, + persistent_id: str | None = None, text_entities: Sequence[MessageEntity] | None = None, + added_by_user: User | None = None, + added_by_chat: Chat | None = None, + addition_date: datetime.datetime | None = None, *, api_kwargs: JSONDict | None = None, ): super().__init__(api_kwargs=api_kwargs) + self.persistent_id: str | None = persistent_id self.text: str = text self.voter_count: int = voter_count + self.added_by_user: User | None = added_by_user + self.added_by_chat: Chat | None = added_by_chat + self.addition_date: int | None = addition_date self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) self._id_attrs = (self.text, self.voter_count) @@ -258,6 +307,10 @@ class PollAnswer(TelegramObject): poll, if the voter is anonymous. .. versionadded:: 20.5 + option_persistent_ids (Sequence[:obj:`str`]): Persistent identifiers of the + chosen answer options. May be empty if the vote was retracted. + + .. versionadded:: NEXT.VERSION Attributes: poll_id (:obj:`str`): Unique poll identifier. @@ -276,10 +329,14 @@ class PollAnswer(TelegramObject): poll, if the voter is anonymous. .. versionadded:: 20.5 + option_persistent_ids (tuple[:obj:`str`]): Persistent identifiers of the + chosen answer options. May be empty if the vote was retracted. + + .. versionadded:: NEXT.VERSION """ - __slots__ = ("option_ids", "poll_id", "user", "voter_chat") + __slots__ = ("option_ids", "option_persistent_ids", "poll_id", "user", "voter_chat") def __init__( self, @@ -287,6 +344,7 @@ def __init__( option_ids: Sequence[int], user: User | None = None, voter_chat: Chat | None = None, + option_persistent_ids: Sequence[str] | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -295,6 +353,7 @@ def __init__( self.voter_chat: Chat | None = voter_chat self.option_ids: tuple[int, ...] = parse_sequence_arg(option_ids) self.user: User | None = user + self.option_persistent_ids: tuple[str, ...] | None = parse_sequence_arg(option_persistent_ids) self._id_attrs = ( self.poll_id, @@ -316,6 +375,176 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollAnswer": return super().de_json(data=data, bot=bot) +class PollOptionAdded(TelegramObject): + """ + Describes a service message about an option added to a poll. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`option_persistent_id`, and :attr:`option_text` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + poll_message (:class:`telegram.MaybeInaccessibleMessage`, optional): Message + containing the poll to which the option was added, if known. + Note that the Message object in this field will not contain the + :attr:`~telegram.Message.reply_to_message` field even if it itself is a reply. + + .. versionadded:: NEXT.VERSION + option_persistent_id (:obj:`str`): Unique identifier of the added option. + + .. versionadded:: NEXT.VERSION + option_text (:obj:`str`, optional): Option text. + + .. versionadded:: NEXT.VERSION + option_text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities that appear in the :paramref:`option_text`. + + .. versionadded:: NEXT.VERSION + + Attributes: + poll_message (:class:`telegram.MaybeInaccessibleMessage`): Optional. Message + containing the poll to which the option was added, if known. + Note that the Message object in this field will not contain the + reply_to_message field even if it itself is a reply. + + .. versionadded:: NEXT.VERSION + option_persistent_id (:obj:`str`): Unique identifier of the added option. + + .. versionadded:: NEXT.VERSION + option_text (:obj:`str`, optional): Option text. + + .. versionadded:: NEXT.VERSION + option_text_entities (tuple[:class:`telegram.MessageEntity`]): Special + entities that appear in the option_text. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = ("option_persistent_id", "option_text", "option_text_entities", "poll_message") + + def __init__( + self, + option_persistent_id: str, + option_text: str, + poll_message: MaybeInaccessibleMessage | None = None, + option_text_entities: Sequence[MessageEntity] | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.option_persistent_id: str = option_persistent_id + self.option_text: str = option_text + self.poll_message: MaybeInaccessibleMessage | None = poll_message + + self.option_text_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + option_text_entities + ) + + self._id_attrs = (self.option_persistent_id,) + + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOptionAdded": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + data["poll_message"] = de_json_optional( + data.get("poll_message"), MaybeInaccessibleMessage, bot + ) + data["option_text_entities"] = de_list_optional( + data.get("option_text_entities"), MessageEntity, bot + ) + + return super().de_json(data=data, bot=bot) + + +class PollOptionDeleted(TelegramObject): + """ + Describes a service message about an option deleted from a poll. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, considered equal, if their :attr:`option_persistent_id`, :attr:`option_text` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + poll_message (:class:`telegram.MaybeInaccessibleMessage`, optional): Message + containing the poll to which the option was deleted, if known. + Note that the Message object in this field will not contain the + reply_to_message field even if it itself is a reply. + + .. versionadded:: NEXT.VERSION + option_persistent_id (:obj:`str`): Unique identifier of the added option. + + .. versionadded:: NEXT.VERSION + option_text (:obj:`str`, optional): Option text. + + .. versionadded:: NEXT.VERSION + option_text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities that appear in the option_text. + + .. versionadded:: NEXT.VERSION + + Attributes: + poll_message (:class:`telegram.MaybeInaccessibleMessage`): Optional. Message + containing the poll to which the option was added, if known. + Note that the Message object in this field will not contain the + reply_to_message field even if it itself is a reply. + + .. versionadded:: NEXT.VERSION + option_persistent_id (:obj:`str`): Unique identifier of the added option. + + .. versionadded:: NEXT.VERSION + option_text (:obj:`str`, optional): Option text. + + .. versionadded:: NEXT.VERSION + option_text_entities (tuple[:class:`telegram.MessageEntity`]): Special + entities that appear in the option_text. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = ("option_persistent_id", "option_text", "option_text_entities", "poll_message") + + def __init__( + self, + option_persistent_id: str, + option_text: str, + poll_message: MaybeInaccessibleMessage | None = None, + option_text_entities: Sequence[MessageEntity] | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.option_persistent_id: str = option_persistent_id + self.option_text: str = option_text + self.poll_message: MaybeInaccessibleMessage | None = poll_message + + self.option_text_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + option_text_entities + ) + + self._id_attrs = (self.option_persistent_id,) + + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOptionDeleted": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + data["poll_message"] = de_json_optional( + data.get("poll_message"), MaybeInaccessibleMessage, bot + ) + data["option_text_entities"] = de_list_optional( + data.get("option_text_entities"), MessageEntity, bot + ) + + return super().de_json(data=data, bot=bot) + + class Poll(TelegramObject): """ This object contains information about a poll. @@ -338,9 +567,26 @@ class Poll(TelegramObject): is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. + allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosen + answer options + + .. versionadded:: NEXT.VERSION correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. + correct_option_ids (Sequence[:class:`int`], optional): Array of 0-based identifiers of + the correct answer options. Available only for polls in quiz mode which are closed or + were sent (not forwarded) by the bot or to the private chat with the bot. + + .. versionadded:: NEXT.VERSION + description (:obj:`str`, optional): Description of the poll; + for polls inside the :class:`~telegram.Message` object only. + + .. versionadded:: NEXT.VERSION + description_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities like usernames, URLs, bot commands, etc. that appear in the description + + .. versionadded:: NEXT.VERSION explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -382,9 +628,26 @@ class Poll(TelegramObject): is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. + allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosen + answer options + + .. versionadded:: NEXT.VERSION correct_option_id (:obj:`int`): Optional. A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. + correct_option_ids (tuple[:class:`int`]): Array of 0-based identifiers of the + correct answer options. Available only for polls in quiz mode which are closed or were + sent (not forwarded) by the bot or to the private chat with the bot. + + .. versionadded:: NEXT.VERSION + description (:obj:`str`): Optional. Description of the poll; + for polls inside the Message object only + + .. versionadded:: NEXT.VERSION + description_entities (tuple[:class:`telegram.MessageEntity`]): Special + entities like usernames, URLs, bot commands, etc. that appear in the description + + .. versionadded:: NEXT.VERSION explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -419,8 +682,12 @@ class Poll(TelegramObject): __slots__ = ( "_open_period", "allows_multiple_answers", + "allows_revoting", "close_date", "correct_option_id", + "correct_option_ids", + "description", + "description_entities", "explanation", "explanation_entities", "id", @@ -443,7 +710,11 @@ def __init__( is_anonymous: bool, type: str, # pylint: disable=redefined-builtin allows_multiple_answers: bool, + allows_revoting: bool, correct_option_id: int | None = None, + correct_option_ids: Sequence[int] | None = None, + description: str | None = None, + description_entities: Sequence[MessageEntity] | None = None, explanation: str | None = None, explanation_entities: Sequence[MessageEntity] | None = None, open_period: TimePeriod | None = None, @@ -461,7 +732,13 @@ def __init__( self.is_anonymous: bool = is_anonymous self.type: str = enum.get_member(constants.PollType, type, type) self.allows_multiple_answers: bool = allows_multiple_answers + self.allows_revoting: bool = allows_revoting self.correct_option_id: int | None = correct_option_id + self.correct_option_ids: tuple[int, ...] | None = parse_sequence_arg(correct_option_ids) + self.description: str | None = description + self.description_entities: tuple[MessageEntity, ...] | None = parse_sequence_arg( + description_entities + ) self.explanation: str | None = explanation self.explanation_entities: tuple[MessageEntity, ...] = parse_sequence_arg( explanation_entities @@ -652,3 +929,8 @@ def parse_question_entities(self, types: list[str] | None = None) -> dict[Messag .. versionadded:: 20.0 """ + MAX_DESCRIPTION_CHARACTERS: Final[int] = constants.PollLimit.MAX_DESCRIPTION_CHARACTERS + """:const:`telegram.constants.PollLimit.MAX_DESCRIPTION_CHARACTERS` + + .. versionadded:: NEXT.VERSION + """ diff --git a/src/telegram/_reply.py b/src/telegram/_reply.py index 367d5aad7a0..f8f8ea395ff 100644 --- a/src/telegram/_reply.py +++ b/src/telegram/_reply.py @@ -410,6 +410,10 @@ class ReplyParameters(TelegramObject): replied to. .. versionadded:: 22.4 + poll_option_id (:obj:`str`, optional): Persistent + identifier of the specific poll option to be replied to. + + .. versionadded:: NEXT.VERSION Attributes: message_id (:obj:`int`): Identifier of the message that will be replied to in the current @@ -437,6 +441,10 @@ class ReplyParameters(TelegramObject): replied to. .. versionadded:: 22.4 + poll_option_id (:obj:`str`): Optional. Persistent + identifier of the specific poll option to be replied to. + + .. versionadded:: NEXT.VERSION """ __slots__ = ( @@ -444,6 +452,7 @@ class ReplyParameters(TelegramObject): "chat_id", "checklist_task_id", "message_id", + "poll_option_id", "quote", "quote_entities", "quote_parse_mode", @@ -460,6 +469,7 @@ def __init__( quote_entities: Sequence[MessageEntity] | None = None, quote_position: int | None = None, checklist_task_id: int | None = None, + poll_option_id: str | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -473,6 +483,7 @@ def __init__( self.quote_entities: tuple[MessageEntity, ...] | None = parse_sequence_arg(quote_entities) self.quote_position: int | None = quote_position self.checklist_task_id: int | None = checklist_task_id + self.poll_option_id: str | None = poll_option_id self._id_attrs = (self.message_id,) diff --git a/src/telegram/_user.py b/src/telegram/_user.py index 89dae50d147..76609a1308d 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -29,6 +29,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, JSONDict, ODVInput, TimePeriod, @@ -1730,9 +1731,17 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, - reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, connect_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1760,6 +1769,9 @@ async def send_poll( type=type, # pylint=pylint, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -1769,6 +1781,8 @@ async def send_poll( write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + message_effect_id=message_effect_id, + allow_paid_broadcast=allow_paid_broadcast, explanation=explanation, explanation_parse_mode=explanation_parse_mode, open_period=open_period, @@ -1781,8 +1795,11 @@ async def send_poll( business_connection_id=business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, - message_effect_id=message_effect_id, - allow_paid_broadcast=allow_paid_broadcast, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_gift( diff --git a/src/telegram/_utils/types.py b/src/telegram/_utils/types.py index 1b29e2e8de6..66785c8be25 100644 --- a/src/telegram/_utils/types.py +++ b/src/telegram/_utils/types.py @@ -93,6 +93,11 @@ CorrectOptionID: TypeAlias = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # pylint: disable=invalid-name +CorrectOptionIds: TypeAlias = tuple[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], ...] +""" +.. versionadded:: NEXT.VERSION +""" + MarkdownVersion: TypeAlias = Literal[1, 2] SocketOpt: TypeAlias = ( diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 4017d77b273..04dbae93570 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -3439,11 +3439,19 @@ class PollLimit(IntEnum): Also used in the :paramref:`~telegram.Bot.send_poll.close_date` parameter of :meth:`telegram.Bot.send_poll`. """ - MAX_OPEN_PERIOD = 600 + MAX_OPEN_PERIOD = 2628000 """:obj:`int`: Maximum value allowed for the :paramref:`~telegram.Bot.send_poll.open_period` parameter of :meth:`telegram.Bot.send_poll`. Also used in the :paramref:`~telegram.Bot.send_poll.close_date` parameter of :meth:`telegram.Bot.send_poll`. + + .. versionadded:: NEXT.VERSION + """ + MAX_DESCRIPTION_CHARACTERS = 1024 + """:obj:`int`: Maximum value allowed for the + :paramref:`~telegram.Bot.send_poll.description` parameter of :meth:`telegram.Bot.send_poll`. + + .. versionadded:: NEXT.VERSION """ diff --git a/src/telegram/ext/_extbot.py b/src/telegram/ext/_extbot.py index c09b1a5f6d4..1c8a7e74961 100644 --- a/src/telegram/ext/_extbot.py +++ b/src/telegram/ext/_extbot.py @@ -98,6 +98,7 @@ from telegram._utils.types import ( BaseUrl, CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -3236,9 +3237,17 @@ async def send_poll( options: Sequence["str | InputPollOption"], is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin - allows_multiple_answers: bool | None = None, correct_option_id: CorrectOptionID | None = None, + allows_multiple_answers: bool | None = None, + allows_revoting: bool | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, is_closed: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -3255,8 +3264,8 @@ async def send_poll( message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, *, - reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, connect_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3295,6 +3304,14 @@ async def send_poll( question_entities=question_entities, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_sticker(