diff --git a/README.rst b/README.rst index ed01ad61723..96901d5f513 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-5.5-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-5.6-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions @@ -111,7 +111,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju Telegram API support ==================== -All types and methods of the Telegram Bot API **5.5** are supported. +All types and methods of the Telegram Bot API **5.6** are supported. ========== Installing diff --git a/README_RAW.rst b/README_RAW.rst index bf78a40ea4b..40cd416e861 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr :target: https://pypi.org/project/python-telegram-bot-raw/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-5.5-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-5.6-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions @@ -105,7 +105,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju Telegram API support ==================== -All types and methods of the Telegram Bot API **5.5** are supported. +All types and methods of the Telegram Bot API **5.6** are supported. ========== Installing diff --git a/telegram/bot.py b/telegram/bot.py index 442b2a0a812..98925288a84 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -306,10 +306,14 @@ def _message( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> Union[bool, Message]: if reply_to_message_id is not None: data['reply_to_message_id'] = reply_to_message_id + if protect_content: + data['protect_content'] = protect_content + # We don't check if (DEFAULT_)None here, so that _put is able to insert the defaults # correctly, if necessary data['disable_notification'] = disable_notification @@ -463,6 +467,7 @@ def send_message( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> Message: """Use this method to send text messages. @@ -480,6 +485,10 @@ def send_message( this message. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of sent messages from + forwarding and saving. + + .. versionadded:: 13.10 reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -519,6 +528,7 @@ def send_message( allow_sending_without_reply=allow_sending_without_reply, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -576,6 +586,7 @@ def forward_message( disable_notification: DVInput[bool] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> Message: """Use this method to forward messages of any kind. Service messages can't be forwarded. @@ -595,6 +606,11 @@ def forward_message( message_id (:obj:`int`): Message identifier in the chat specified in from_chat_id. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the read timeout from the server (instead of the one specified during creation of the connection pool). @@ -616,13 +632,13 @@ def forward_message( data['from_chat_id'] = from_chat_id if message_id: data['message_id'] = message_id - return self._message( # type: ignore[return-value] 'forwardMessage', data, disable_notification=disable_notification, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -640,6 +656,7 @@ def send_photo( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> Message: """Use this method to send photos. @@ -674,6 +691,11 @@ def send_photo( :attr:`parse_mode`. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -713,6 +735,7 @@ def send_photo( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -734,6 +757,7 @@ def send_audio( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> Message: """ Use this method to send audio files, if you want Telegram clients to display them in the @@ -778,6 +802,11 @@ def send_audio( title (:obj:`str`, optional): Track name. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -834,6 +863,7 @@ def send_audio( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -853,6 +883,7 @@ def send_document( disable_content_type_detection: bool = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> Message: """ Use this method to send general files. @@ -891,6 +922,11 @@ def send_document( :attr:`parse_mode`. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -943,6 +979,7 @@ def send_document( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -956,6 +993,7 @@ def send_sticker( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """ Use this method to send static .WEBP or animated .TGS stickers. @@ -978,6 +1016,11 @@ def send_sticker( Accept :obj:`bytes` as input. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1007,6 +1050,7 @@ def send_sticker( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1029,6 +1073,7 @@ def send_video( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> Message: """ Use this method to send video files, Telegram clients support mp4 videos @@ -1076,6 +1121,11 @@ def send_video( suitable for streaming. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1133,6 +1183,7 @@ def send_video( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1150,6 +1201,7 @@ def send_video_note( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, filename: str = None, + protect_content: bool = None, ) -> Message: """ As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. @@ -1184,6 +1236,11 @@ def send_video_note( message. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1232,6 +1289,7 @@ def send_video_note( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1253,6 +1311,7 @@ def send_animation( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> Message: """ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). @@ -1303,6 +1362,11 @@ def send_animation( :attr:`parse_mode`. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1349,6 +1413,7 @@ def send_animation( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1367,6 +1432,7 @@ def send_voice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> Message: """ Use this method to send audio files, if you want Telegram clients to display the file @@ -1406,6 +1472,11 @@ def send_voice( duration (:obj:`int`, optional): Duration of the voice message in seconds. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1447,6 +1518,7 @@ def send_voice( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1461,6 +1533,7 @@ def send_media_group( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> List[Message]: """Use this method to send a group of photos or videos as an album. @@ -1472,6 +1545,11 @@ def send_media_group( describing messages to be sent, must include 2–10 items. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1503,6 +1581,9 @@ def send_media_group( if reply_to_message_id: data['reply_to_message_id'] = reply_to_message_id + if protect_content: + data['protect_content'] = protect_content + result = self._post('sendMediaGroup', data, timeout=timeout, api_kwargs=api_kwargs) return Message.de_list(result, self) # type: ignore @@ -1524,6 +1605,7 @@ def send_location( heading: int = None, proximity_alert_radius: int = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """Use this method to send point on the map. @@ -1547,6 +1629,11 @@ def send_location( between 1 and 100000 if specified. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1601,6 +1688,7 @@ def send_location( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1761,6 +1849,7 @@ def send_venue( google_place_id: str = None, google_place_type: str = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """Use this method to send information about a venue. @@ -1790,6 +1879,11 @@ def send_venue( venue (:class:`telegram.Venue`, optional): The venue to send. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1852,6 +1946,7 @@ def send_venue( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1869,6 +1964,7 @@ def send_contact( vcard: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """Use this method to send phone contacts. @@ -1887,6 +1983,11 @@ def send_contact( contact (:class:`telegram.Contact`, optional): The contact to send. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1938,6 +2039,7 @@ def send_contact( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -1951,6 +2053,7 @@ def send_game( timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """Use this method to send a game. @@ -1960,6 +2063,11 @@ def send_game( for the game. Set up your games via `@BotFather `_. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -1991,6 +2099,7 @@ def send_game( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -3505,6 +3614,7 @@ def send_invoice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, max_tip_amount: int = None, suggested_tip_amounts: List[int] = None, + protect_content: bool = None, ) -> Message: """Use this method to send invoices. @@ -3579,6 +3689,11 @@ def send_invoice( the shipping method. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -3651,6 +3766,7 @@ def send_invoice( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -5021,6 +5137,7 @@ def send_poll( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> Message: """ Use this method to send a native poll. @@ -5059,6 +5176,11 @@ def send_poll( immediately closed. This can be useful for poll preview. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -5118,6 +5240,7 @@ def send_poll( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -5177,6 +5300,7 @@ def send_dice( emoji: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> Message: """ Use this method to send an animated emoji that will display a random value. @@ -5193,6 +5317,11 @@ def send_dice( Added the "🎳" emoji. disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -5213,9 +5342,7 @@ def send_dice( :class:`telegram.error.TelegramError` """ - data: JSONDict = { - 'chat_id': chat_id, - } + data: JSONDict = {'chat_id': chat_id} if emoji: data['emoji'] = emoji @@ -5229,6 +5356,7 @@ def send_dice( reply_markup=reply_markup, allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, + protect_content=protect_content, ) @log @@ -5454,6 +5582,7 @@ def copy_message( reply_markup: ReplyMarkup = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> MessageId: """ Use this method to copy messages of any kind. Service messages and invoice messages can't @@ -5475,6 +5604,11 @@ def copy_message( parse_mode disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. + protect_content (:obj:`bool`, optional): Protects the contents of the sent message from + forwarding and saving. + + .. versionadded:: 13.10 + reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message @@ -5508,6 +5642,8 @@ def copy_message( data['caption_entities'] = caption_entities if reply_to_message_id: data['reply_to_message_id'] = reply_to_message_id + if protect_content: + data['protect_content'] = protect_content if reply_markup: if isinstance(reply_markup, ReplyMarkup): # We need to_json() instead of to_dict() here, because reply_markups may be diff --git a/telegram/callbackquery.py b/telegram/callbackquery.py index 47b05b97129..a47a7280f37 100644 --- a/telegram/callbackquery.py +++ b/telegram/callbackquery.py @@ -620,6 +620,7 @@ def copy_message( reply_markup: ReplyMarkup = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -648,6 +649,7 @@ def copy_message( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) MAX_ANSWER_TEXT_LENGTH: ClassVar[int] = constants.MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH diff --git a/telegram/chat.py b/telegram/chat.py index 0997ac0b688..84ef53909f4 100644 --- a/telegram/chat.py +++ b/telegram/chat.py @@ -792,6 +792,7 @@ def send_message( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -815,6 +816,7 @@ def send_message( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def send_media_group( @@ -827,6 +829,7 @@ def send_media_group( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> List['Message']: """Shortcut for:: @@ -846,6 +849,7 @@ def send_media_group( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_chat_action( @@ -887,6 +891,7 @@ def send_photo( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -911,6 +916,7 @@ def send_photo( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_contact( @@ -926,6 +932,7 @@ def send_contact( vcard: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -950,6 +957,7 @@ def send_contact( vcard=vcard, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_audio( @@ -969,6 +977,7 @@ def send_audio( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -997,6 +1006,7 @@ def send_audio( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_document( @@ -1014,6 +1024,7 @@ def send_document( disable_content_type_detection: bool = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1040,6 +1051,7 @@ def send_document( disable_content_type_detection=disable_content_type_detection, allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, + protect_content=protect_content, ) def send_dice( @@ -1051,6 +1063,7 @@ def send_dice( emoji: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1071,6 +1084,7 @@ def send_dice( emoji=emoji, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_game( @@ -1082,6 +1096,7 @@ def send_game( timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1102,6 +1117,7 @@ def send_game( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_invoice( @@ -1133,6 +1149,7 @@ def send_invoice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, max_tip_amount: int = None, suggested_tip_amounts: List[int] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1181,6 +1198,7 @@ def send_invoice( allow_sending_without_reply=allow_sending_without_reply, max_tip_amount=max_tip_amount, suggested_tip_amounts=suggested_tip_amounts, + protect_content=protect_content, ) def send_location( @@ -1198,6 +1216,7 @@ def send_location( heading: int = None, proximity_alert_radius: int = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1224,6 +1243,7 @@ def send_location( heading=heading, proximity_alert_radius=proximity_alert_radius, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_animation( @@ -1243,6 +1263,7 @@ def send_animation( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1271,6 +1292,7 @@ def send_animation( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_sticker( @@ -1282,6 +1304,7 @@ def send_sticker( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1302,6 +1325,7 @@ def send_sticker( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_venue( @@ -1321,6 +1345,7 @@ def send_venue( google_place_id: str = None, google_place_type: str = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1349,6 +1374,7 @@ def send_venue( google_place_id=google_place_id, google_place_type=google_place_type, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_video( @@ -1369,6 +1395,7 @@ def send_video( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1398,6 +1425,7 @@ def send_video( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_video_note( @@ -1413,6 +1441,7 @@ def send_video_note( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1437,6 +1466,7 @@ def send_video_note( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, filename=filename, + protect_content=protect_content, ) def send_voice( @@ -1453,6 +1483,7 @@ def send_voice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1478,6 +1509,7 @@ def send_voice( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_poll( @@ -1501,6 +1533,7 @@ def send_poll( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1532,6 +1565,7 @@ def send_poll( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, explanation_entities=explanation_entities, + protect_content=protect_content, ) def send_copy( @@ -1547,6 +1581,7 @@ def send_copy( reply_markup: 'ReplyMarkup' = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1571,6 +1606,7 @@ def send_copy( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def copy_message( @@ -1586,6 +1622,7 @@ def copy_message( reply_markup: 'ReplyMarkup' = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1610,6 +1647,7 @@ def copy_message( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def export_invite_link( diff --git a/telegram/constants.py b/telegram/constants.py index 58e0f946c07..a9f3464a466 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -21,7 +21,7 @@ `Telegram Bots API `_. Attributes: - BOT_API_VERSION (:obj:`str`): `5.5`. Telegram Bot API version supported by this + BOT_API_VERSION (:obj:`str`): `5.6`. Telegram Bot API version supported by this version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``. .. versionadded:: 13.4 @@ -141,6 +141,9 @@ MESSAGEENTITY_TEXT_MENTION (:obj:`str`): ``'text_mention'`` MESSAGEENTITY_UNDERLINE (:obj:`str`): ``'underline'`` MESSAGEENTITY_STRIKETHROUGH (:obj:`str`): ``'strikethrough'`` + MESSAGEENTITY_SPOILER (:obj:`str`): ``'spoiler'`` + + .. versionadded:: 13.10 MESSAGEENTITY_ALL_TYPES (List[:obj:`str`]): List of all the types of message entity. :class:`telegram.ParseMode`: @@ -244,7 +247,7 @@ """ from typing import List -BOT_API_VERSION: str = '5.5' +BOT_API_VERSION: str = '5.6' MAX_MESSAGE_LENGTH: int = 4096 MAX_CAPTION_LENGTH: int = 1024 ANONYMOUS_ADMIN_ID: int = 1087968824 @@ -321,6 +324,7 @@ MESSAGEENTITY_TEXT_MENTION: str = 'text_mention' MESSAGEENTITY_UNDERLINE: str = 'underline' MESSAGEENTITY_STRIKETHROUGH: str = 'strikethrough' +MESSAGEENTITY_SPOILER: str = 'spoiler' MESSAGEENTITY_ALL_TYPES: List[str] = [ MESSAGEENTITY_MENTION, MESSAGEENTITY_HASHTAG, @@ -337,6 +341,7 @@ MESSAGEENTITY_TEXT_MENTION, MESSAGEENTITY_UNDERLINE, MESSAGEENTITY_STRIKETHROUGH, + MESSAGEENTITY_SPOILER, ] PARSEMODE_MARKDOWN: str = 'Markdown' diff --git a/telegram/ext/extbot.py b/telegram/ext/extbot.py index 5c51458cd2e..6831fba21a2 100644 --- a/telegram/ext/extbot.py +++ b/telegram/ext/extbot.py @@ -193,6 +193,7 @@ def _message( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> Union[bool, Message]: # We override this method to call self._replace_keyboard and self._insert_callback_data. # This covers most methods that have a reply_markup @@ -205,6 +206,7 @@ def _message( allow_sending_without_reply=allow_sending_without_reply, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) if isinstance(result, Message): self._insert_callback_data(result) @@ -299,6 +301,7 @@ def copy_message( reply_markup: ReplyMarkup = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> MessageId: # We override this method to call self._replace_keyboard return super().copy_message( @@ -314,6 +317,7 @@ def copy_message( reply_markup=self._replace_keyboard(reply_markup), timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def get_chat( diff --git a/telegram/message.py b/telegram/message.py index 7b439cff237..ce15d2aa6f7 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -769,6 +769,7 @@ def reply_text( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -799,6 +800,7 @@ def reply_text( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def reply_markdown( @@ -813,6 +815,7 @@ def reply_markdown( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -853,6 +856,7 @@ def reply_markdown( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def reply_markdown_v2( @@ -867,6 +871,7 @@ def reply_markdown_v2( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -903,6 +908,7 @@ def reply_markdown_v2( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def reply_html( @@ -917,6 +923,7 @@ def reply_html( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -953,6 +960,7 @@ def reply_html( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def reply_media_group( @@ -966,6 +974,7 @@ def reply_media_group( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> List['Message']: """Shortcut for:: @@ -994,6 +1003,7 @@ def reply_media_group( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_photo( @@ -1010,6 +1020,7 @@ def reply_photo( caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1041,6 +1052,7 @@ def reply_photo( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def reply_audio( @@ -1061,6 +1073,7 @@ def reply_audio( caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1096,6 +1109,7 @@ def reply_audio( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def reply_document( @@ -1114,6 +1128,7 @@ def reply_document( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1147,6 +1162,7 @@ def reply_document( disable_content_type_detection=disable_content_type_detection, allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, + protect_content=protect_content, ) def reply_animation( @@ -1167,6 +1183,7 @@ def reply_animation( caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1202,6 +1219,7 @@ def reply_animation( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def reply_sticker( @@ -1214,6 +1232,7 @@ def reply_sticker( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1241,6 +1260,7 @@ def reply_sticker( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_video( @@ -1262,6 +1282,7 @@ def reply_video( caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1298,6 +1319,7 @@ def reply_video( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def reply_video_note( @@ -1314,6 +1336,7 @@ def reply_video_note( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1345,6 +1368,7 @@ def reply_video_note( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, filename=filename, + protect_content=protect_content, ) def reply_voice( @@ -1362,6 +1386,7 @@ def reply_voice( caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1394,6 +1419,7 @@ def reply_voice( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def reply_location( @@ -1412,6 +1438,7 @@ def reply_location( proximity_alert_radius: int = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1445,6 +1472,7 @@ def reply_location( heading=heading, proximity_alert_radius=proximity_alert_radius, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_venue( @@ -1465,6 +1493,7 @@ def reply_venue( google_place_type: str = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1500,6 +1529,7 @@ def reply_venue( google_place_id=google_place_id, google_place_type=google_place_type, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_contact( @@ -1516,6 +1546,7 @@ def reply_contact( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1547,6 +1578,7 @@ def reply_contact( vcard=vcard, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_poll( @@ -1570,6 +1602,7 @@ def reply_poll( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1608,6 +1641,7 @@ def reply_poll( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, explanation_entities=explanation_entities, + protect_content=protect_content, ) def reply_dice( @@ -1620,6 +1654,7 @@ def reply_dice( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1647,6 +1682,7 @@ def reply_dice( emoji=emoji, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_chat_action( @@ -1684,6 +1720,7 @@ def reply_game( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, quote: bool = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1713,6 +1750,7 @@ def reply_game( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def reply_invoice( @@ -1745,6 +1783,7 @@ def reply_invoice( quote: bool = None, max_tip_amount: int = None, suggested_tip_amounts: List[int] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1802,6 +1841,7 @@ def reply_invoice( allow_sending_without_reply=allow_sending_without_reply, max_tip_amount=max_tip_amount, suggested_tip_amounts=suggested_tip_amounts, + protect_content=protect_content, ) def forward( @@ -1810,6 +1850,7 @@ def forward( disable_notification: DVInput[bool] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1840,6 +1881,7 @@ def forward( disable_notification=disable_notification, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def copy( @@ -1854,6 +1896,7 @@ def copy( reply_markup: ReplyMarkup = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1882,6 +1925,7 @@ def copy( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def reply_copy( @@ -1898,6 +1942,7 @@ def reply_copy( timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, quote: bool = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1935,6 +1980,7 @@ def reply_copy( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def edit_text( @@ -2531,6 +2577,8 @@ def _parse_html( insert = '' + text + '' elif entity.type == MessageEntity.STRIKETHROUGH: insert = '' + text + '' + elif entity.type == MessageEntity.SPOILER: + insert = f'{text}' else: insert = text @@ -2583,6 +2631,9 @@ def text_html(self) -> str: Use this if you want to retrieve the message text with the entities formatted as HTML in the same way the original message was formatted. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as HTML. + Returns: :obj:`str`: Message text with entities formatted as HTML. @@ -2596,6 +2647,9 @@ def text_html_urled(self) -> str: Use this if you want to retrieve the message text with the entities formatted as HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as HTML. + Returns: :obj:`str`: Message text with entities formatted as HTML. @@ -2610,9 +2664,11 @@ def caption_html(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as HTML in the same way the original message was formatted. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as HTML. + Returns: :obj:`str`: Message caption with caption entities formatted as HTML. - """ return self._parse_html(self.caption, self.parse_caption_entities(), urled=False) @@ -2624,9 +2680,11 @@ def caption_html_urled(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as HTML. + Returns: :obj:`str`: Message caption with caption entities formatted as HTML. - """ return self._parse_html(self.caption, self.parse_caption_entities(), urled=True) @@ -2735,6 +2793,12 @@ def _parse_markdown( 'Strikethrough entities are not supported for Markdown ' 'version 1' ) insert = '~' + text + '~' + elif entity.type == MessageEntity.SPOILER: + if version == 1: + raise ValueError( + "Spoiler entities are not supported for Markdown version 1" + ) + insert = f"||{text}||" else: insert = text @@ -2804,6 +2868,10 @@ def text_markdown(self) -> str: Returns: :obj:`str`: Message text with entities formatted as Markdown. + Raises: + :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested + entities. + """ return self._parse_markdown(self.text, self.parse_entities(), urled=False) @@ -2815,9 +2883,11 @@ def text_markdown_v2(self) -> str: Use this if you want to retrieve the message text with the entities formatted as Markdown in the same way the original message was formatted. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as Markdown V2. + Returns: :obj:`str`: Message text with entities formatted as Markdown. - """ return self._parse_markdown(self.text, self.parse_entities(), urled=False, version=2) @@ -2836,6 +2906,10 @@ def text_markdown_urled(self) -> str: Returns: :obj:`str`: Message text with entities formatted as Markdown. + Raises: + :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested + entities. + """ return self._parse_markdown(self.text, self.parse_entities(), urled=True) @@ -2847,9 +2921,11 @@ def text_markdown_v2_urled(self) -> str: Use this if you want to retrieve the message text with the entities formatted as Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as Markdown V2. + Returns: :obj:`str`: Message text with entities formatted as Markdown. - """ return self._parse_markdown(self.text, self.parse_entities(), urled=True, version=2) @@ -2868,6 +2944,10 @@ def caption_markdown(self) -> str: Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. + Raises: + :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested + entities. + """ return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=False) @@ -2879,9 +2959,11 @@ def caption_markdown_v2(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as Markdown in the same way the original message was formatted. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as Markdown V2. + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. - """ return self._parse_markdown( self.caption, self.parse_caption_entities(), urled=False, version=2 @@ -2902,6 +2984,10 @@ def caption_markdown_urled(self) -> str: Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. + Raises: + :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested + entities. + """ return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=True) @@ -2913,9 +2999,11 @@ def caption_markdown_v2_urled(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. + .. versionchanged:: 13.10 + Spoiler entities are now formatted as Markdown V2. + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. - """ return self._parse_markdown( self.caption, self.parse_caption_entities(), urled=True, version=2 diff --git a/telegram/messageentity.py b/telegram/messageentity.py index 0a0350eebbc..992a099fc3a 100644 --- a/telegram/messageentity.py +++ b/telegram/messageentity.py @@ -36,10 +36,11 @@ class MessageEntity(TelegramObject): considered equal, if their :attr:`type`, :attr:`offset` and :attr:`length` are equal. Args: - type (:obj:`str`): Type of the entity. Can be mention (@username), hashtag, bot_command, - url, email, phone_number, bold (bold text), italic (italic text), strikethrough, - code (monowidth string), pre (monowidth block), text_link (for clickable text URLs), - text_mention (for users without usernames). + type (:obj:`str`): Type of the entity. Currently, can be mention (@username), hashtag, + bot_command, url, email, phone_number, bold (bold text), italic (italic text), + strikethrough, spoiler (spoiler message), code (monowidth string), pre + (monowidth block), text_link (for clickable text URLs), text_mention + (for users without usernames). offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity. length (:obj:`int`): Length of the entity in UTF-16 code units. url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after @@ -124,6 +125,11 @@ def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MessageEntit """:const:`telegram.constants.MESSAGEENTITY_UNDERLINE`""" STRIKETHROUGH: ClassVar[str] = constants.MESSAGEENTITY_STRIKETHROUGH """:const:`telegram.constants.MESSAGEENTITY_STRIKETHROUGH`""" + SPOILER: ClassVar[str] = constants.MESSAGEENTITY_SPOILER + """:const:`telegram.constants.MESSAGEENTITY_SPOILER` + + .. versionadded:: 13.10 + """ ALL_TYPES: ClassVar[List[str]] = constants.MESSAGEENTITY_ALL_TYPES """:const:`telegram.constants.MESSAGEENTITY_ALL_TYPES`\n List of all the types""" diff --git a/telegram/user.py b/telegram/user.py index ce0af46ac36..7676faf11ac 100644 --- a/telegram/user.py +++ b/telegram/user.py @@ -338,6 +338,7 @@ def send_message( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -361,6 +362,7 @@ def send_message( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, entities=entities, + protect_content=protect_content, ) def send_photo( @@ -376,6 +378,7 @@ def send_photo( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -400,6 +403,7 @@ def send_photo( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_media_group( @@ -412,6 +416,7 @@ def send_media_group( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> List['Message']: """Shortcut for:: @@ -431,6 +436,7 @@ def send_media_group( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_audio( @@ -450,6 +456,7 @@ def send_audio( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -478,6 +485,7 @@ def send_audio( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_chat_action( @@ -519,6 +527,7 @@ def send_contact( vcard: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -543,6 +552,7 @@ def send_contact( vcard=vcard, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_dice( @@ -554,6 +564,7 @@ def send_dice( emoji: str = None, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -574,6 +585,7 @@ def send_dice( emoji=emoji, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_document( @@ -591,6 +603,7 @@ def send_document( disable_content_type_detection: bool = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -617,6 +630,7 @@ def send_document( disable_content_type_detection=disable_content_type_detection, allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, + protect_content=protect_content, ) def send_game( @@ -628,6 +642,7 @@ def send_game( timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -648,6 +663,7 @@ def send_game( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_invoice( @@ -679,6 +695,7 @@ def send_invoice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, max_tip_amount: int = None, suggested_tip_amounts: List[int] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -727,6 +744,7 @@ def send_invoice( allow_sending_without_reply=allow_sending_without_reply, max_tip_amount=max_tip_amount, suggested_tip_amounts=suggested_tip_amounts, + protect_content=protect_content, ) def send_location( @@ -744,6 +762,7 @@ def send_location( heading: int = None, proximity_alert_radius: int = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -770,6 +789,7 @@ def send_location( heading=heading, proximity_alert_radius=proximity_alert_radius, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_animation( @@ -789,6 +809,7 @@ def send_animation( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -817,6 +838,7 @@ def send_animation( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_sticker( @@ -828,6 +850,7 @@ def send_sticker( timeout: DVInput[float] = DEFAULT_20, api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -848,6 +871,7 @@ def send_sticker( timeout=timeout, api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_video( @@ -868,6 +892,7 @@ def send_video( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -897,6 +922,7 @@ def send_video( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_venue( @@ -916,6 +942,7 @@ def send_venue( google_place_id: str = None, google_place_type: str = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -944,6 +971,7 @@ def send_venue( google_place_id=google_place_id, google_place_type=google_place_type, allow_sending_without_reply=allow_sending_without_reply, + protect_content=protect_content, ) def send_video_note( @@ -959,6 +987,7 @@ def send_video_note( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -983,6 +1012,7 @@ def send_video_note( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, filename=filename, + protect_content=protect_content, ) def send_voice( @@ -999,6 +1029,7 @@ def send_voice( allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, filename: str = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1024,6 +1055,7 @@ def send_voice( allow_sending_without_reply=allow_sending_without_reply, caption_entities=caption_entities, filename=filename, + protect_content=protect_content, ) def send_poll( @@ -1047,6 +1079,7 @@ def send_poll( api_kwargs: JSONDict = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, + protect_content: bool = None, ) -> 'Message': """Shortcut for:: @@ -1078,6 +1111,7 @@ def send_poll( api_kwargs=api_kwargs, allow_sending_without_reply=allow_sending_without_reply, explanation_entities=explanation_entities, + protect_content=protect_content, ) def send_copy( @@ -1093,6 +1127,7 @@ def send_copy( reply_markup: 'ReplyMarkup' = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1117,6 +1152,7 @@ def send_copy( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def copy_message( @@ -1132,6 +1168,7 @@ def copy_message( reply_markup: 'ReplyMarkup' = None, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, + protect_content: bool = None, ) -> 'MessageId': """Shortcut for:: @@ -1156,6 +1193,7 @@ def copy_message( reply_markup=reply_markup, timeout=timeout, api_kwargs=api_kwargs, + protect_content=protect_content, ) def approve_join_request( diff --git a/tests/test_animation.py b/tests/test_animation.py index b90baeafbb1..28d007ee49a 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -89,6 +89,7 @@ def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file caption=self.caption, parse_mode='Markdown', disable_notification=False, + protect_content=True, thumb=thumb_file, ) @@ -102,6 +103,7 @@ def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file assert message.animation.file_size == animation.file_size assert message.animation.thumb.width == self.width assert message.animation.thumb.height == self.height + assert message.has_protected_content @flaky(3, 1) def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): diff --git a/tests/test_audio.py b/tests/test_audio.py index 924c7220f63..c3da58b845d 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -95,6 +95,7 @@ def test_send_all_args(self, bot, chat_id, audio_file, thumb_file): performer=self.performer, title=self.title, disable_notification=False, + protect_content=True, parse_mode='Markdown', thumb=thumb_file, ) @@ -115,6 +116,7 @@ def test_send_all_args(self, bot, chat_id, audio_file, thumb_file): assert message.audio.thumb.file_size == self.thumb_file_size assert message.audio.thumb.width == self.thumb_width assert message.audio.thumb.height == self.thumb_height + assert message.has_protected_content @flaky(3, 1) def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch): diff --git a/tests/test_bot.py b/tests/test_bot.py index bfda0555f74..27ad4971f67 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -324,6 +324,20 @@ def test_forward_message(self, bot, chat_id, message): assert forward_message.forward_from.username == message.from_user.username assert isinstance(forward_message.forward_date, dtm.datetime) + def test_forward_protected_message(self, bot, message, chat_id): + to_forward_protected = bot.send_message(chat_id, 'cant forward me', protect_content=True) + assert to_forward_protected.has_protected_content + + with pytest.raises(BadRequest, match="can't be forwarded"): + to_forward_protected.forward(chat_id) + + to_forward_unprotected = bot.send_message(chat_id, 'forward me', protect_content=False) + assert not to_forward_unprotected.has_protected_content + forwarded_but_now_protected = to_forward_unprotected.forward(chat_id, protect_content=True) + assert forwarded_but_now_protected.has_protected_content + with pytest.raises(BadRequest, match="can't be forwarded"): + forwarded_but_now_protected.forward(chat_id) + @flaky(3, 1) def test_delete_message(self, bot, chat_id): message = bot.send_message(chat_id, text='will be deleted') @@ -360,6 +374,7 @@ def test_send_venue(self, bot, chat_id): longitude=longitude, foursquare_id=foursquare_id, foursquare_type=foursquare_type, + protect_content=True, ) assert message.venue @@ -371,6 +386,7 @@ def test_send_venue(self, bot, chat_id): assert message.venue.foursquare_type == foursquare_type assert message.venue.google_place_id is None assert message.venue.google_place_type is None + assert message.has_protected_content message = bot.send_venue( chat_id=chat_id, @@ -380,6 +396,7 @@ def test_send_venue(self, bot, chat_id): longitude=longitude, google_place_id=google_place_id, google_place_type=google_place_type, + protect_content=True, ) assert message.venue @@ -391,6 +408,7 @@ def test_send_venue(self, bot, chat_id): assert message.venue.google_place_type == google_place_type assert message.venue.foursquare_id is None assert message.venue.foursquare_type is None + assert message.has_protected_content @flaky(3, 1) @pytest.mark.xfail(raises=RetryAfter) @@ -402,13 +420,18 @@ def test_send_contact(self, bot, chat_id): first_name = 'Leandro' last_name = 'Toledo' message = bot.send_contact( - chat_id=chat_id, phone_number=phone_number, first_name=first_name, last_name=last_name + chat_id=chat_id, + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + protect_content=True, ) assert message.contact assert message.contact.phone_number == phone_number assert message.contact.first_name == first_name assert message.contact.last_name == last_name + assert message.has_protected_content # TODO: Add bot to group to test polls too @@ -435,6 +458,7 @@ def test_send_and_stop_poll(self, bot, super_group_id, reply_markup): is_anonymous=False, allows_multiple_answers=True, timeout=60, + protect_content=True, ) assert message.poll @@ -446,6 +470,7 @@ def test_send_and_stop_poll(self, bot, super_group_id, reply_markup): assert message.poll.allows_multiple_answers assert not message.poll.is_closed assert message.poll.type == Poll.REGULAR + assert message.has_protected_content # Since only the poll and not the complete message is returned, we can't check that the # reply_markup is correct. So we just test that sending doesn't give an error. @@ -664,9 +689,10 @@ def test_send_poll_default_allow_sending_without_reply(self, default_bot, chat_i @flaky(3, 1) @pytest.mark.parametrize('emoji', Dice.ALL_EMOJI + [None]) def test_send_dice(self, bot, chat_id, emoji): - message = bot.send_dice(chat_id, emoji=emoji) + message = bot.send_dice(chat_id, emoji=emoji, protect_content=True) assert message.dice + assert message.has_protected_content if emoji is None: assert message.dice.emoji == Dice.DICE else: @@ -1414,7 +1440,7 @@ def test_delete_chat_sticker_set(self): @flaky(3, 1) def test_send_game(self, bot, chat_id): game_short_name = 'test_game' - message = bot.send_game(chat_id, game_short_name) + message = bot.send_game(chat_id, game_short_name, protect_content=True) assert message.game assert message.game.description == ( @@ -1424,6 +1450,7 @@ def test_send_game(self, bot, chat_id): # We added some test bots later and for some reason the file size is not the same for them # so we accept three different sizes here. Shouldn't be too much of assert message.game.photo[0].file_size in [851, 4928, 850] + assert message.has_protected_content @flaky(3, 1) @pytest.mark.parametrize( @@ -1980,11 +2007,12 @@ def request_wrapper(*args, **kwargs): @flaky(3, 1) def test_send_message_entities(self, bot, chat_id): - test_string = 'Italic Bold Code' + test_string = 'Italic Bold Code Spoiler' entities = [ MessageEntity(MessageEntity.ITALIC, 0, 6), MessageEntity(MessageEntity.ITALIC, 7, 4), MessageEntity(MessageEntity.ITALIC, 12, 4), + MessageEntity(MessageEntity.SPOILER, 17, 7), ] message = bot.send_message(chat_id=chat_id, text=test_string, entities=entities) assert message.text == test_string @@ -2150,6 +2178,7 @@ def post(url, data, timeout): assert data["reply_markup"] == keyboard.to_json() assert data["disable_notification"] is True assert data["caption_entities"] == [MessageEntity(MessageEntity.BOLD, 0, 4)] + assert data['protect_content'] is True return data monkeypatch.setattr(bot.request, 'post', post) @@ -2163,6 +2192,7 @@ def post(url, data, timeout): reply_to_message_id=media_message.message_id, reply_markup=keyboard.to_json() if json_keyboard else keyboard, disable_notification=True, + protect_content=True, ) @flaky(3, 1) diff --git a/tests/test_document.py b/tests/test_document.py index fa00faf6ea1..82d8f55241c 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -83,6 +83,7 @@ def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file): document=document_file, caption=self.caption, disable_notification=False, + protect_content=True, filename='telegram_custom.png', parse_mode='Markdown', thumb=thumb_file, @@ -100,6 +101,7 @@ def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file): assert message.caption == self.caption.replace('*', '') assert message.document.thumb.width == self.thumb_width assert message.document.thumb.height == self.thumb_height + assert message.has_protected_content @flaky(3, 1) def test_get_and_download(self, bot, document): diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index a23d9698731..7eccd141071 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -462,7 +462,11 @@ def test_send_media_group_photo(self, bot, chat_id, media_group): def test_send_media_group_all_args(self, bot, chat_id, media_group): m1 = bot.send_message(chat_id, text="test") messages = bot.send_media_group( - chat_id, media_group, disable_notification=True, reply_to_message_id=m1.message_id + chat_id, + media_group, + disable_notification=True, + reply_to_message_id=m1.message_id, + protect_content=True, ) assert isinstance(messages, list) assert len(messages) == 3 @@ -472,6 +476,7 @@ def test_send_media_group_all_args(self, bot, chat_id, media_group): assert all( mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages ) + assert all(mes.has_protected_content for mes in messages) @flaky(3, 1) def test_send_media_group_custom_filename( diff --git a/tests/test_invoice.py b/tests/test_invoice.py index 92377f40d11..47b9e5385c9 100644 --- a/tests/test_invoice.py +++ b/tests/test_invoice.py @@ -127,6 +127,8 @@ def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch): send_phone_number_to_provider=True, send_email_to_provider=True, is_flexible=True, + disable_notification=True, + protect_content=True, ) assert message.invoice.currency == self.currency @@ -134,6 +136,7 @@ def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch): assert message.invoice.description == self.description assert message.invoice.title == self.title assert message.invoice.total_amount == self.total_amount + assert message.has_protected_content # We do this next one as safety guard to make sure that we pass all of the optional # parameters correctly because #2526 went unnoticed for 3 years … @@ -188,6 +191,8 @@ def make_assertion(*args, **_): send_phone_number_to_provider='send_phone_number_to_provider', send_email_to_provider='send_email_to_provider', is_flexible='is_flexible', + disable_notification=True, + protect_content=True, ) def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token): diff --git a/tests/test_location.py b/tests/test_location.py index 20cd46a1192..d58da13c6ba 100644 --- a/tests/test_location.py +++ b/tests/test_location.py @@ -80,6 +80,7 @@ def test_send_live_location(self, bot, chat_id): horizontal_accuracy=50, heading=90, proximity_alert_radius=1000, + protect_content=True, ) assert message.location assert pytest.approx(52.223880, message.location.latitude) @@ -88,6 +89,7 @@ def test_send_live_location(self, bot, chat_id): assert message.location.horizontal_accuracy == 50 assert message.location.heading == 90 assert message.location.proximity_alert_radius == 1000 + assert message.has_protected_content message2 = bot.edit_message_live_location( message.chat_id, diff --git a/tests/test_message.py b/tests/test_message.py index 1810502ac9f..95085196ff4 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -285,10 +285,11 @@ class TestMessage: {'length': 29, 'offset': 91, 'type': 'bold'}, {'length': 9, 'offset': 101, 'type': 'strikethrough'}, {'length': 10, 'offset': 129, 'type': 'pre', 'language': 'python'}, + {'length': 7, 'offset': 141, 'type': 'spoiler'}, ] test_text_v2 = ( r'Test for trgh nested in italic. Python pre.' + 'http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled.' ) test_message = Message( message_id=1, @@ -388,7 +389,8 @@ def test_text_html_simple(self): 'text-mention and ' r'
`\pre
. http://google.com ' 'and bold nested in strk>trgh nested in italic. ' - '
Python pre
.' + '
Python pre
. ' + 'Spoiled.' ) text_html = self.test_message_v2.text_html assert text_html == test_html_string @@ -406,7 +408,8 @@ def test_text_html_urled(self): 'text-mention and ' r'
`\pre
. http://google.com ' 'and bold nested in strk>trgh nested in italic. ' - '
Python pre
.' + '
Python pre
. ' + 'Spoiled.' ) text_html = self.test_message_v2.text_html_urled assert text_html == test_html_string @@ -427,7 +430,7 @@ def test_text_markdown_v2_simple(self): '[links](http://github.com/abc\\\\\\)def), ' '[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. ' r'http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. ' - '```python\nPython pre```\\.' + '```python\nPython pre```\\. ||Spoiled||\\.' ) text_markdown = self.test_message_v2.text_markdown_v2 assert text_markdown == test_md_string @@ -449,6 +452,10 @@ def test_text_markdown_new_in_v2(self, message): with pytest.raises(ValueError): message.text_markdown + message.entities = [MessageEntity(MessageEntity.SPOILER, offset=0, length=4)] + with pytest.raises(ValueError): + message.text_markdown + message.entities = [] def test_text_markdown_empty(self, message): @@ -473,7 +480,7 @@ def test_text_markdown_v2_urled(self): '[links](http://github.com/abc\\\\\\)def), ' '[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. ' r'[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ ' - 'nested in* italic_\\. ```python\nPython pre```\\.' + 'nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\.' ) text_markdown = self.test_message_v2.text_markdown_v2_urled assert text_markdown == test_md_string @@ -504,7 +511,8 @@ def test_caption_html_simple(self): 'text-mention and ' r'
`\pre
. http://google.com ' 'and bold nested in strk>trgh nested in italic. ' - '
Python pre
.' + '
Python pre
. ' + 'Spoiled.' ) caption_html = self.test_message_v2.caption_html assert caption_html == test_html_string @@ -522,7 +530,8 @@ def test_caption_html_urled(self): 'text-mention and ' r'
`\pre
. http://google.com ' 'and bold nested in strk>trgh nested in italic. ' - '
Python pre
.' + '
Python pre
. ' + 'Spoiled.' ) caption_html = self.test_message_v2.caption_html_urled assert caption_html == test_html_string @@ -543,7 +552,7 @@ def test_caption_markdown_v2_simple(self): '[links](http://github.com/abc\\\\\\)def), ' '[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. ' r'http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. ' - '```python\nPython pre```\\.' + '```python\nPython pre```\\. ||Spoiled||\\.' ) caption_markdown = self.test_message_v2.caption_markdown_v2 assert caption_markdown == test_md_string @@ -570,7 +579,7 @@ def test_caption_markdown_v2_urled(self): '[links](http://github.com/abc\\\\\\)def), ' '[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. ' r'[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ ' - 'nested in* italic_\\. ```python\nPython pre```\\.' + 'nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\.' ) caption_markdown = self.test_message_v2.caption_markdown_v2_urled assert caption_markdown == test_md_string @@ -726,7 +735,7 @@ def test_reply_markdown_v2(self, monkeypatch, message): '[links](http://github.com/abc\\\\\\)def), ' '[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. ' r'http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. ' - '```python\nPython pre```\\.' + '```python\nPython pre```\\. ||Spoiled||\\.' ) def make_assertion(*_, **kwargs): @@ -765,7 +774,8 @@ def test_reply_html(self, monkeypatch, message): 'text-mention and ' r'
`\pre
. http://google.com ' 'and bold nested in strk>trgh nested in italic. ' - '
Python pre
.' + '
Python pre
. ' + 'Spoiled.' ) def make_assertion(*_, **kwargs): @@ -1136,14 +1146,15 @@ def make_assertion(*_, **kwargs): quote=True, ) - @pytest.mark.parametrize('disable_notification', [False, True]) - def test_forward(self, monkeypatch, message, disable_notification): + @pytest.mark.parametrize('disable_notification,protected', [(False, True), (True, False)]) + def test_forward(self, monkeypatch, message, disable_notification, protected): def make_assertion(*_, **kwargs): chat_id = kwargs['chat_id'] == 123456 from_chat = kwargs['from_chat_id'] == message.chat_id message_id = kwargs['message_id'] == message.message_id notification = kwargs['disable_notification'] == disable_notification - return chat_id and from_chat and message_id and notification + protected_cont = kwargs['protect_content'] == protected + return chat_id and from_chat and message_id and notification and protected_cont assert check_shortcut_signature( Message.forward, Bot.forward_message, ['from_chat_id', 'message_id'], [] @@ -1152,11 +1163,13 @@ def make_assertion(*_, **kwargs): assert check_defaults_handling(message.forward, message.bot) monkeypatch.setattr(message.bot, 'forward_message', make_assertion) - assert message.forward(123456, disable_notification=disable_notification) + assert message.forward( + 123456, disable_notification=disable_notification, protect_content=protected + ) assert not message.forward(635241) - @pytest.mark.parametrize('disable_notification', [True, False]) - def test_copy(self, monkeypatch, message, disable_notification): + @pytest.mark.parametrize('disable_notification,protected', [(True, False), (False, True)]) + def test_copy(self, monkeypatch, message, disable_notification, protected): keyboard = [[1, 2]] def make_assertion(*_, **kwargs): @@ -1164,11 +1177,19 @@ def make_assertion(*_, **kwargs): from_chat = kwargs['from_chat_id'] == message.chat_id message_id = kwargs['message_id'] == message.message_id notification = kwargs['disable_notification'] == disable_notification + protected_cont = kwargs['protect_content'] == protected if kwargs.get('reply_markup') is not None: reply_markup = kwargs['reply_markup'] is keyboard else: reply_markup = True - return chat_id and from_chat and message_id and notification and reply_markup + return ( + chat_id + and from_chat + and message_id + and notification + and reply_markup + and protected_cont + ) assert check_shortcut_signature( Message.copy, Bot.copy_message, ['from_chat_id', 'message_id'], [] @@ -1177,14 +1198,19 @@ def make_assertion(*_, **kwargs): assert check_defaults_handling(message.copy, message.bot) monkeypatch.setattr(message.bot, 'copy_message', make_assertion) - assert message.copy(123456, disable_notification=disable_notification) assert message.copy( - 123456, reply_markup=keyboard, disable_notification=disable_notification + 123456, disable_notification=disable_notification, protect_content=protected + ) + assert message.copy( + 123456, + reply_markup=keyboard, + disable_notification=disable_notification, + protect_content=protected, ) assert not message.copy(635241) - @pytest.mark.parametrize('disable_notification', [True, False]) - def test_reply_copy(self, monkeypatch, message, disable_notification): + @pytest.mark.parametrize('disable_notification,protected', [(True, False), (False, True)]) + def test_reply_copy(self, monkeypatch, message, disable_notification, protected): keyboard = [[1, 2]] def make_assertion(*_, **kwargs): @@ -1192,6 +1218,7 @@ def make_assertion(*_, **kwargs): from_chat = kwargs['chat_id'] == message.chat_id message_id = kwargs['message_id'] == 456789 notification = kwargs['disable_notification'] == disable_notification + is_protected = kwargs['protect_content'] == protected if kwargs.get('reply_markup') is not None: reply_markup = kwargs['reply_markup'] is keyboard else: @@ -1200,7 +1227,15 @@ def make_assertion(*_, **kwargs): reply = kwargs['reply_to_message_id'] == message.message_id else: reply = True - return chat_id and from_chat and message_id and notification and reply_markup and reply + return ( + chat_id + and from_chat + and message_id + and notification + and reply_markup + and reply + and is_protected + ) assert check_shortcut_signature( Message.reply_copy, Bot.copy_message, ['chat_id'], ['quote'] @@ -1209,12 +1244,22 @@ def make_assertion(*_, **kwargs): assert check_defaults_handling(message.copy, message.bot) monkeypatch.setattr(message.bot, 'copy_message', make_assertion) - assert message.reply_copy(123456, 456789, disable_notification=disable_notification) assert message.reply_copy( - 123456, 456789, reply_markup=keyboard, disable_notification=disable_notification + 123456, 456789, disable_notification=disable_notification, protect_content=protected + ) + assert message.reply_copy( + 123456, + 456789, + reply_markup=keyboard, + disable_notification=disable_notification, + protect_content=protected, ) assert message.reply_copy( - 123456, 456789, quote=True, disable_notification=disable_notification + 123456, + 456789, + quote=True, + disable_notification=disable_notification, + protect_content=protected, ) assert message.reply_copy( 123456, @@ -1222,6 +1267,7 @@ def make_assertion(*_, **kwargs): quote=True, reply_to_message_id=message.message_id, disable_notification=disable_notification, + protect_content=protected, ) def test_edit_text(self, monkeypatch, message): diff --git a/tests/test_messageentity.py b/tests/test_messageentity.py index 2f632c073c1..124d92c56b9 100644 --- a/tests/test_messageentity.py +++ b/tests/test_messageentity.py @@ -31,9 +31,9 @@ def message_entity(request): if type_ == MessageEntity.TEXT_MENTION: user = User(1, 'test_user', False) language = None - if type == MessageEntity.PRE: + if type_ == MessageEntity.PRE: language = "python" - return MessageEntity(type, 1, 3, url=url, user=user, language=language) + return MessageEntity(type_, 1, 3, url=url, user=user, language=language) class TestMessageEntity: diff --git a/tests/test_photo.py b/tests/test_photo.py index d6096056df5..a27d00bfab3 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -103,6 +103,7 @@ def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo): photo_file, caption=self.caption, disable_notification=False, + protect_content=True, parse_mode='Markdown', ) @@ -125,6 +126,7 @@ def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo): assert message.photo[1].file_size == photo.file_size assert message.caption == TestPhoto.caption.replace('*', '') + assert message.has_protected_content @flaky(3, 1) def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch): diff --git a/tests/test_sticker.py b/tests/test_sticker.py index bb614b939e5..aa81a5f82d5 100644 --- a/tests/test_sticker.py +++ b/tests/test_sticker.py @@ -106,7 +106,9 @@ def test_expected_values(self, sticker): @flaky(3, 1) def test_send_all_args(self, bot, chat_id, sticker_file, sticker): - message = bot.send_sticker(chat_id, sticker=sticker_file, disable_notification=False) + message = bot.send_sticker( + chat_id, sticker=sticker_file, disable_notification=False, protect_content=True + ) assert isinstance(message.sticker, Sticker) assert isinstance(message.sticker.file_id, str) @@ -126,6 +128,7 @@ def test_send_all_args(self, bot, chat_id, sticker_file, sticker): assert message.sticker.thumb.width == sticker.thumb.width assert message.sticker.thumb.height == sticker.thumb.height assert message.sticker.thumb.file_size == sticker.thumb.file_size + assert message.has_protected_content @flaky(3, 1) def test_get_and_download(self, bot, sticker): diff --git a/tests/test_video.py b/tests/test_video.py index 0eca16798ea..210555576dd 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -98,6 +98,7 @@ def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): caption=self.caption, supports_streaming=self.supports_streaming, disable_notification=False, + protect_content=True, width=video.width, height=video.height, parse_mode='Markdown', @@ -121,6 +122,7 @@ def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): assert message.video.thumb.height == self.thumb_height assert message.video.file_name == self.file_name + assert message.has_protected_content @flaky(3, 1) def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch): diff --git a/tests/test_videonote.py b/tests/test_videonote.py index 7f8c39773fb..a59903cef2c 100644 --- a/tests/test_videonote.py +++ b/tests/test_videonote.py @@ -88,6 +88,7 @@ def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_fi duration=self.duration, length=self.length, disable_notification=False, + protect_content=True, thumb=thumb_file, ) @@ -103,6 +104,7 @@ def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_fi assert message.video_note.thumb.file_size == self.thumb_file_size assert message.video_note.thumb.width == self.thumb_width assert message.video_note.thumb.height == self.thumb_height + assert message.has_protected_content @flaky(3, 1) def test_send_video_note_custom_filename(self, bot, chat_id, video_note_file, monkeypatch): diff --git a/tests/test_voice.py b/tests/test_voice.py index df45da699fd..b5a3014f52e 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -81,6 +81,7 @@ def test_send_all_args(self, bot, chat_id, voice_file, voice): duration=self.duration, caption=self.caption, disable_notification=False, + protect_content=True, parse_mode='Markdown', ) @@ -93,6 +94,7 @@ def test_send_all_args(self, bot, chat_id, voice_file, voice): assert message.voice.mime_type == voice.mime_type assert message.voice.file_size == voice.file_size assert message.caption == self.caption.replace('*', '') + assert message.has_protected_content @flaky(3, 1) def test_send_voice_custom_filename(self, bot, chat_id, voice_file, monkeypatch):