From fd0e1b847f4fa1de7ea375329ebb8035a58f57e4 Mon Sep 17 00:00:00 2001 From: OhYoung <212045739+ouyooung@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:55:20 +0800 Subject: [PATCH 1/6] Updates to the poll section in bot API 9.6 --- src/telegram/_bot.py | 36 +++++++++++++++++++---- src/telegram/_chat.py | 12 +++++--- src/telegram/_message.py | 10 +++++-- src/telegram/_poll.py | 57 ++++++++++++++++++++++++++++++------ src/telegram/_user.py | 16 ++++++---- src/telegram/_utils/types.py | 5 +++- src/telegram/constants.py | 4 ++- 7 files changed, 111 insertions(+), 29 deletions(-) diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index 1050f05baf7..22552cade73 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -101,7 +101,7 @@ from telegram._utils.strings import to_camel_case from telegram._utils.types import ( BaseUrl, - CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -7597,8 +7597,12 @@ async def send_poll( 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_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, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -7650,8 +7654,26 @@ async def send_poll( :tg-const:`telegram.Poll.REGULAR`, defaults to :tg-const:`telegram.Poll.REGULAR`. allows_multiple_answers (:obj:`bool`, optional): :obj:`True`, if the poll allows 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 + + .. 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): 0-based identifier of the + correct answer option, 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 @@ -7755,7 +7777,11 @@ async def send_poll( "is_anonymous": is_anonymous, "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, "explanation": explanation, "explanation_entities": explanation_entities, diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 1230bf48277..4ac6bd3f2f0 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -33,7 +33,7 @@ from telegram._utils import enum from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( - CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -2279,8 +2279,10 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, allows_multiple_answers: bool | None = None, - correct_option_id: CorrectOptionID | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, is_closed: bool | None = None, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -2297,8 +2299,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, @@ -2322,7 +2324,9 @@ async def send_poll( is_anonymous=is_anonymous, 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, diff --git a/src/telegram/_message.py b/src/telegram/_message.py index c0d018ec406..fbb3726bb0f 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -79,7 +79,7 @@ from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.strings import TextEncoding from telegram._utils.types import ( - CorrectOptionID, + CorrectOptionIds, JSONDict, MarkdownVersion, ODVInput, @@ -3457,8 +3457,10 @@ async def reply_poll( 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_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, is_closed: bool | None = None, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -3521,7 +3523,9 @@ async def reply_poll( is_anonymous=is_anonymous, 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, diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 744edd22eff..dbada9d6bee 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -338,9 +338,23 @@ 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. - 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. + allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosen + answer options + + .. versionadded:: NEXT.VERSION + 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 +396,23 @@ 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. - 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. + allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosen + answer options + + .. versionadded:: NEXT.VERSION + 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 +447,11 @@ 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 +474,10 @@ def __init__( is_anonymous: bool, type: str, # pylint: disable=redefined-builtin allows_multiple_answers: bool, - correct_option_id: int | None = None, + allows_revoting: bool, + 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 +495,12 @@ 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.correct_option_id: int | None = correct_option_id + self.allows_revoting: bool = allows_revoting + self.correct_option_ids: tuple[int, ...] | None = parse_sequence_arg(correct_option_ids) + self.description: str | None = description + self.description_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + description_entities + ) self.explanation: str | None = explanation self.explanation_entities: tuple[MessageEntity, ...] = parse_sequence_arg( explanation_entities diff --git a/src/telegram/_user.py b/src/telegram/_user.py index 89dae50d147..1f088816e70 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -28,7 +28,7 @@ from telegram._telegramobject import TelegramObject from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( - CorrectOptionID, + CorrectOptionIds, JSONDict, ODVInput, TimePeriod, @@ -1713,8 +1713,10 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, allows_multiple_answers: bool | None = None, - correct_option_id: CorrectOptionID | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, is_closed: bool | None = None, + shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -1731,8 +1733,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, @@ -1759,7 +1761,9 @@ async def send_poll( is_anonymous=is_anonymous, 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 +1773,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 +1787,6 @@ 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, ) async def send_gift( diff --git a/src/telegram/_utils/types.py b/src/telegram/_utils/types.py index 1b29e2e8de6..d73f0bc64f5 100644 --- a/src/telegram/_utils/types.py +++ b/src/telegram/_utils/types.py @@ -91,7 +91,10 @@ .. versionadded:: 20.4""" -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], ...] # pylint: disable=invalid-name +""" +.. versionadded:: NEXT.VERSION +""" MarkdownVersion: TypeAlias = Literal[1, 2] diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 4017d77b273..a35139ea9d0 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -3439,11 +3439,13 @@ 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 """ From f10126b8cad4f5f86b0f411bc10c1f7413d7bd28 Mon Sep 17 00:00:00 2001 From: OhYoung <212045739+ouyooung@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:17:49 +0800 Subject: [PATCH 2/6] Add chango --- changes/unreleased/5202.ouyoung.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changes/unreleased/5202.ouyoung.toml diff --git a/changes/unreleased/5202.ouyoung.toml b/changes/unreleased/5202.ouyoung.toml new file mode 100644 index 00000000000..d7234b7c395 --- /dev/null +++ b/changes/unreleased/5202.ouyoung.toml @@ -0,0 +1,5 @@ +features = "Updates to the poll section in bot API 9.6" + +pull_requests = [ + { uid = "5202", author_uid = "ouyooung" }, +] \ No newline at end of file From 348de470335f1ea978299ed1676d74905b7cc6e0 Mon Sep 17 00:00:00 2001 From: OhYoung <212045739+ouyooung@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:19:04 +0800 Subject: [PATCH 3/6] Reformat changelog --- changes/unreleased/5202.ouyoung.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/unreleased/5202.ouyoung.toml b/changes/unreleased/5202.ouyoung.toml index d7234b7c395..d7ced58d804 100644 --- a/changes/unreleased/5202.ouyoung.toml +++ b/changes/unreleased/5202.ouyoung.toml @@ -2,4 +2,4 @@ features = "Updates to the poll section in bot API 9.6" pull_requests = [ { uid = "5202", author_uid = "ouyooung" }, -] \ No newline at end of file +] From 6f9967f310289fb5b78b6f80f88a2900ea0a85e2 Mon Sep 17 00:00:00 2001 From: OhYoung <212045739+ouyooung@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:24:21 +0800 Subject: [PATCH 4/6] The poll section has been updated. --- src/telegram/__init__.py | 11 +- src/telegram/_bot.py | 26 ++++ src/telegram/_chat.py | 19 ++- src/telegram/_message.py | 62 +++++++-- src/telegram/_poll.py | 248 ++++++++++++++++++++++++++++++++++- src/telegram/_reply.py | 11 ++ src/telegram/_user.py | 19 ++- src/telegram/_utils/types.py | 4 +- src/telegram/constants.py | 8 +- src/telegram/ext/_extbot.py | 21 ++- 10 files changed, 408 insertions(+), 21 deletions(-) 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 22552cade73..9fe007d232c 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -101,6 +101,7 @@ from telegram._utils.strings import to_camel_case from telegram._utils.types import ( BaseUrl, + CorrectOptionID, CorrectOptionIds, FileInput, JSONDict, @@ -7596,12 +7597,16 @@ async def send_poll( options: Sequence["str | InputPollOption"], is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin + 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, @@ -7654,6 +7659,8 @@ async def send_poll( :tg-const:`telegram.Poll.REGULAR`, defaults to :tg-const:`telegram.Poll.REGULAR`. allows_multiple_answers (:obj:`bool`, optional): :obj:`True`, if the poll allows 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 @@ -7704,6 +7711,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 description_parse_mode + + .. versionadded:: NEXT.VERSION disable_notification (:obj:`bool`, optional): |disable_notification| protect_content (:obj:`bool`, optional): |protect_content| @@ -7777,12 +7799,16 @@ async def send_poll( "is_anonymous": is_anonymous, "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 4ac6bd3f2f0..15a9dcf742e 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -33,6 +33,7 @@ from telegram._utils import enum from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( + CorrectOptionID, CorrectOptionIds, FileInput, JSONDict, @@ -2279,10 +2280,8 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, allows_multiple_answers: bool | None = None, - allows_revoting: bool | None = None, - correct_option_ids: CorrectOptionIds | None = None, + correct_option_id: CorrectOptionID | None = None, is_closed: bool | None = None, - shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -2298,6 +2297,14 @@ 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, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: int | None = None, @@ -2324,6 +2331,7 @@ async def send_poll( is_anonymous=is_anonymous, 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, @@ -2350,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 fbb3726bb0f..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 @@ -79,6 +79,7 @@ from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.strings import TextEncoding from telegram._utils.types import ( + CorrectOptionID, CorrectOptionIds, JSONDict, MarkdownVersion, @@ -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 @@ -3457,33 +3491,39 @@ async def reply_poll( is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin allows_multiple_answers: bool | None = None, - allows_revoting: bool | None = None, - correct_option_ids: CorrectOptionIds | None = None, + correct_option_id: CorrectOptionID | None = None, is_closed: bool | None = None, - shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, 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:: @@ -3523,6 +3563,7 @@ async def reply_poll( is_anonymous=is_anonymous, 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, @@ -3547,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 dbada9d6bee..71a4245884c 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:`int`, optional): Point in time (Unix timestamp) + 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:`int`): Optional. Point in time (Unix timestamp) + 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: int | 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, ...] = parse_sequence_arg(option_persistent_ids) self._id_attrs = ( self.poll_id, @@ -316,6 +375,177 @@ 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` is 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 + 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) -> "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` is 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 + 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. @@ -342,6 +572,9 @@ class Poll(TelegramObject): 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. @@ -400,6 +633,9 @@ class Poll(TelegramObject): 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. @@ -449,6 +685,7 @@ class Poll(TelegramObject): "allows_multiple_answers", "allows_revoting", "close_date", + "correct_option_id", "correct_option_ids", "description", "description_entities", @@ -475,6 +712,7 @@ def __init__( 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, @@ -496,6 +734,7 @@ def __init__( 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, ...] = parse_sequence_arg( @@ -691,3 +930,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 1f088816e70..76609a1308d 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -28,6 +28,7 @@ from telegram._telegramobject import TelegramObject from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( + CorrectOptionID, CorrectOptionIds, JSONDict, ODVInput, @@ -1713,10 +1714,8 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, allows_multiple_answers: bool | None = None, - allows_revoting: bool | None = None, - correct_option_ids: CorrectOptionIds | None = None, + correct_option_id: CorrectOptionID | None = None, is_closed: bool | None = None, - shuffle_options: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "ReplyMarkup | None" = None, explanation: str | None = None, @@ -1732,6 +1731,14 @@ 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, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: int | None = None, @@ -1761,6 +1768,7 @@ async def send_poll( is_anonymous=is_anonymous, 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, @@ -1787,6 +1795,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_gift( diff --git a/src/telegram/_utils/types.py b/src/telegram/_utils/types.py index d73f0bc64f5..66785c8be25 100644 --- a/src/telegram/_utils/types.py +++ b/src/telegram/_utils/types.py @@ -91,7 +91,9 @@ .. versionadded:: 20.4""" -CorrectOptionIds: TypeAlias = tuple[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], ...] # pylint: disable=invalid-name +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 """ diff --git a/src/telegram/constants.py b/src/telegram/constants.py index a35139ea9d0..04dbae93570 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -3444,7 +3444,13 @@ class PollLimit(IntEnum): :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( From c848c17f993367c601931629c4f9cad602dcb08a Mon Sep 17 00:00:00 2001 From: OuYoung <212045739+ouyooung@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:08:22 +0800 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> --- src/telegram/_bot.py | 7 +++---- src/telegram/_poll.py | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index 9fe007d232c..cfa3a765b24 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -7662,7 +7662,7 @@ async def send_poll( 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 + 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 @@ -7677,8 +7677,7 @@ async def send_poll( must be shown only after the poll closes .. versionadded:: NEXT.VERSION - correct_option_ids (Sequence[:class:`int`], optional): 0-based identifier of the - correct answer option, required for polls in quiz mode. + 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 @@ -7723,7 +7722,7 @@ async def send_poll( .. 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 description_parse_mode + which can be specified instead of :paramref:`description_parse_mode` .. versionadded:: NEXT.VERSION disable_notification (:obj:`bool`, optional): |disable_notification| diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 71a4245884c..20f77b3cea9 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -135,7 +135,7 @@ class PollOption(TelegramObject): omitted if the option wasn't added by a chat after poll creation. .. versionadded:: NEXT.VERSION - addition_date (:obj:`int`, optional): Point in time (Unix timestamp) + 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 @@ -162,7 +162,7 @@ class PollOption(TelegramObject): omitted if the option wasn't added by a chat after poll creation. .. versionadded:: NEXT.VERSION - addition_date (:obj:`int`): Optional. Point in time (Unix timestamp) + 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 @@ -193,7 +193,7 @@ def __init__( text_entities: Sequence[MessageEntity] | None = None, added_by_user: User | None = None, added_by_chat: Chat | None = None, - addition_date: int | None = None, + addition_date: datetime.datetime | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -353,7 +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, ...] = parse_sequence_arg(option_persistent_ids) + self.option_persistent_ids: tuple[str, ...] | None = parse_sequence_arg(option_persistent_ids) self._id_attrs = ( self.poll_id, @@ -380,7 +380,7 @@ 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` is equal. + considered equal, if their :attr:`option_persistent_id`, and :attr:`option_text` are equal. .. versionadded:: NEXT.VERSION @@ -388,7 +388,7 @@ class PollOptionAdded(TelegramObject): 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. + :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. @@ -398,7 +398,7 @@ class PollOptionAdded(TelegramObject): .. versionadded:: NEXT.VERSION option_text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special - entities that appear in the option_text. + entities that appear in the :paramref:`option_text`. .. versionadded:: NEXT.VERSION @@ -464,15 +464,14 @@ 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` is equal. + 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 added, if known. + 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. @@ -737,7 +736,7 @@ def __init__( 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, ...] = parse_sequence_arg( + self.description_entities: tuple[MessageEntity, ...] | None = parse_sequence_arg( description_entities ) self.explanation: str | None = explanation From a5c214e62f6727fe655ebd31264846dc006119a8 Mon Sep 17 00:00:00 2001 From: OhYoung <212045739+ouyooung@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:08:50 +0800 Subject: [PATCH 6/6] Reformat changelog --- changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml | 1 + changes/unreleased/5202.ouyoung.toml | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 changes/unreleased/5202.ouyoung.toml 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/changes/unreleased/5202.ouyoung.toml b/changes/unreleased/5202.ouyoung.toml deleted file mode 100644 index d7ced58d804..00000000000 --- a/changes/unreleased/5202.ouyoung.toml +++ /dev/null @@ -1,5 +0,0 @@ -features = "Updates to the poll section in bot API 9.6" - -pull_requests = [ - { uid = "5202", author_uid = "ouyooung" }, -]