From da9b409135c2caa1ab1d24b7e78b04a411ecf502 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 30 Dec 2022 14:37:51 +0100 Subject: [PATCH 01/23] Feat: API 6.4 initial implementation --- docs/source/telegram.at-tree.rst | 4 + docs/source/telegram.forumtopicedited.rst | 6 + .../telegram.generalforumtopichidden.rst | 6 + .../telegram.generalforumtopicunhidden.rst | 6 + docs/source/telegram.writeaccessallowed.rst | 6 + docs/substitutions/global.rst | 2 + telegram/__init__.py | 15 ++- telegram/_bot.py | 37 +++++-- telegram/_chat.py | 36 +++++- telegram/_forumtopic.py | 60 ++++++++++ telegram/_message.py | 97 +++++++++++++++-- telegram/_replykeyboardmarkup.py | 30 +++++ telegram/_user.py | 6 + telegram/_writeaccessallowed.py | 31 ++++++ telegram/ext/_extbot.py | 12 +- tests/test_animation.py | 2 + tests/test_bot.py | 12 ++ tests/test_chat.py | 12 ++ tests/test_forum.py | 103 +++++++++++++++++- tests/test_photo.py | 2 + tests/test_replykeyboardmarkup.py | 4 + tests/test_video.py | 2 + tests/test_writeaccessallowed.py | 38 +++++++ 23 files changed, 507 insertions(+), 22 deletions(-) create mode 100644 docs/source/telegram.forumtopicedited.rst create mode 100644 docs/source/telegram.generalforumtopichidden.rst create mode 100644 docs/source/telegram.generalforumtopicunhidden.rst create mode 100644 docs/source/telegram.writeaccessallowed.rst create mode 100644 telegram/_writeaccessallowed.py create mode 100644 tests/test_writeaccessallowed.py diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 122df9befcc..dd9f1c44266 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -39,7 +39,10 @@ Available Types telegram.forumtopic telegram.forumtopicclosed telegram.forumtopiccreated + telegram.forumtopicedited telegram.forumtopicreopened + telegram.generalforumtopichidden + telegram.generalforumtopicunhidden telegram.inlinekeyboardbutton telegram.inlinekeyboardmarkup telegram.inputfile @@ -84,4 +87,5 @@ Available Types telegram.webappdata telegram.webappinfo telegram.webhookinfo + telegram.writeaccessallowed diff --git a/docs/source/telegram.forumtopicedited.rst b/docs/source/telegram.forumtopicedited.rst new file mode 100644 index 00000000000..77dfb349170 --- /dev/null +++ b/docs/source/telegram.forumtopicedited.rst @@ -0,0 +1,6 @@ +telegram.ForumTopicEdited +========================= + +.. autoclass:: telegram.ForumTopicEdited + :members: + :show-inheritance: diff --git a/docs/source/telegram.generalforumtopichidden.rst b/docs/source/telegram.generalforumtopichidden.rst new file mode 100644 index 00000000000..d3843ab6ce5 --- /dev/null +++ b/docs/source/telegram.generalforumtopichidden.rst @@ -0,0 +1,6 @@ +telegram.GeneralForumTopicHidden +================================ + +.. autoclass:: telegram.GeneralForumTopicHidden + :members: + :show-inheritance: diff --git a/docs/source/telegram.generalforumtopicunhidden.rst b/docs/source/telegram.generalforumtopicunhidden.rst new file mode 100644 index 00000000000..924ddc74262 --- /dev/null +++ b/docs/source/telegram.generalforumtopicunhidden.rst @@ -0,0 +1,6 @@ +telegram.GeneralForumTopicUnhidden +================================== + +.. autoclass:: telegram.GeneralForumTopicUnhidden + :members: + :show-inheritance: diff --git a/docs/source/telegram.writeaccessallowed.rst b/docs/source/telegram.writeaccessallowed.rst new file mode 100644 index 00000000000..9487a24b2d6 --- /dev/null +++ b/docs/source/telegram.writeaccessallowed.rst @@ -0,0 +1,6 @@ +telegram.WriteAccessAllowed +=========================== + +.. autoclass:: telegram.WriteAccessAllowed + :members: + :show-inheritance: diff --git a/docs/substitutions/global.rst b/docs/substitutions/global.rst index 233c2ca27a0..579bc903d5e 100644 --- a/docs/substitutions/global.rst +++ b/docs/substitutions/global.rst @@ -47,3 +47,5 @@ .. |tupleclassattrs| replace:: This attribute is now an immutable tuple. .. |alwaystuple| replace:: This attribute is now always a tuple, that may be empty. + +.. |has_spoiler| replace:: Pass :obj:`True` if the photo needs to be covered with a spoiler animation. \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index c0230f076c8..60eda7ab225 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -70,9 +70,12 @@ "ForumTopic", "ForumTopicClosed", "ForumTopicCreated", + "ForumTopicEdited", "ForumTopicReopened", "Game", "GameHighScore", + "GeneralForumTopicHidden", + "GeneralForumTopicUnhidden", "helpers", "IdDocumentData", "InlineKeyboardButton", @@ -176,6 +179,7 @@ "WebAppData", "WebAppInfo", "WebhookInfo", + "WriteAccessAllowed", ) @@ -234,7 +238,15 @@ from ._files.videonote import VideoNote from ._files.voice import Voice from ._forcereply import ForceReply -from ._forumtopic import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened +from ._forumtopic import ( + ForumTopic, + ForumTopicClosed, + ForumTopicCreated, + ForumTopicEdited, + ForumTopicReopened, + GeneralForumTopicHidden, + GeneralForumTopicUnhidden, +) from ._games.callbackgame import CallbackGame from ._games.game import Game from ._games.gamehighscore import GameHighScore @@ -326,6 +338,7 @@ from ._webappdata import WebAppData from ._webappinfo import WebAppInfo from ._webhookinfo import WebhookInfo +from ._writeaccessallowed import WriteAccessAllowed #: :obj:`str`: The version of the `python-telegram-bot` library as string. #: To get detailed information about the version number, please use :data:`__version_info__` diff --git a/telegram/_bot.py b/telegram/_bot.py index 332b8785e3a..1a1f7626423 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -918,6 +918,7 @@ async def send_photo( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -962,6 +963,9 @@ async def send_photo( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + + .. versionadded:: 20.0 Keyword Args: filename (:obj:`str`, optional): Custom file name for the photo, when uploading a @@ -980,6 +984,7 @@ async def send_photo( data: JSONDict = { "chat_id": chat_id, "photo": self._parse_file_input(photo, PhotoSize, filename=filename), + "has_spoiler": has_spoiler, } return await self._send_message( @@ -1341,6 +1346,7 @@ async def send_video( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1411,6 +1417,9 @@ async def send_video( .. versionchanged:: 20.0 File paths as input is also accepted for bots *not* running in :paramref:`~telegram.Bot.local_mode`. + has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + + .. versionadded:: 20.0 Keyword Args: filename (:obj:`str`, optional): Custom file name for the video, when uploading a @@ -1434,6 +1443,7 @@ async def send_video( "height": height, "supports_streaming": supports_streaming, "thumb": self._parse_file_input(thumb, attach=True) if thumb else None, + "has_spoiler": has_spoiler, } return await self._send_message( @@ -1589,6 +1599,7 @@ async def send_animation( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1654,6 +1665,9 @@ async def send_animation( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + + .. versionadded:: 20.0 Keyword Args: filename (:obj:`str`, optional): Custom file name for the animation, when uploading a @@ -1676,6 +1690,7 @@ async def send_animation( "width": width, "height": height, "thumb": self._parse_file_input(thumb, attach=True) if thumb else None, + "has_spoiler": has_spoiler, } return await self._send_message( @@ -2507,6 +2522,7 @@ async def send_chat_action( self, chat_id: Union[str, int], action: str, + message_thread_id: int = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2528,6 +2544,9 @@ async def send_chat_action( action(:obj:`str`): Type of action to broadcast. Choose one, depending on what the user is about to receive. For convenience look at the constants in :class:`telegram.constants.ChatAction`. + message_thread_id (:obj:`int`, optional): |message_thread_id_arg| + + .. versionadded:: 20.0 Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -2536,7 +2555,11 @@ async def send_chat_action( :class:`telegram.error.TelegramError` """ - data: JSONDict = {"chat_id": chat_id, "action": action} + data: JSONDict = { + "chat_id": chat_id, + "action": action, + "message_thread_id": message_thread_id, + } result = await self._post( "sendChatAction", data, @@ -6874,8 +6897,8 @@ async def edit_forum_topic( self, chat_id: Union[str, int], message_thread_id: int, - name: str, - icon_custom_emoji_id: str, + name: str = None, + icon_custom_emoji_id: str = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -6897,12 +6920,12 @@ async def edit_forum_topic( Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_group| message_thread_id (:obj:`int`): |message_thread_id| - name (:obj:`str`): New topic name, + name (:obj:`str`, optional): New topic name, :tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`- :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. - icon_custom_emoji_id (:obj:`str`): New unique identifier of the custom emoji shown as - the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` to get all - allowed custom emoji identifiers. + icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji + shown as the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` + to get all allowed custom emoji identifiers. Returns: :obj:`bool`: On success, :obj:`True` is returned. diff --git a/telegram/_chat.py b/telegram/_chat.py index 7cdd9c79386..fa52dbdaae5 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -164,6 +164,16 @@ class Chat(TelegramObject): status of the other party in a private chat. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: 20.0 + has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive + anti-spam checks are enabled in the supergroup. The field is only available to chat + administrators. Returned only in :meth:`telegram.Bot.get_chat`. + + .. versionadded:: 20.0 + has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only + get the list of bots and administrators in the chat. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: 20.0 Attributes: @@ -247,6 +257,16 @@ class Chat(TelegramObject): status of the other party in a private chat. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: 20.0 + has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive + anti-spam checks are enabled in the supergroup. The field is only available to chat + administrators. Returned only in :meth:`telegram.Bot.get_chat`. + + .. versionadded:: 20.0 + has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only + get the list of bots and administrators in the chat. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: 20.0 .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups @@ -279,6 +299,8 @@ class Chat(TelegramObject): "is_forum", "active_usernames", "emoji_status_custom_emoji_id", + "has_hidden_members", + "has_aggressive_anti_spam_enabled", ) SENDER: ClassVar[str] = constants.ChatType.SENDER @@ -323,6 +345,8 @@ def __init__( is_forum: bool = None, active_usernames: Sequence[str] = None, emoji_status_custom_emoji_id: str = None, + has_aggressive_anti_spam_enabled: bool = None, + has_hidden_members: bool = None, *, api_kwargs: JSONDict = None, ): @@ -357,6 +381,8 @@ def __init__( self.is_forum = is_forum self.active_usernames = parse_sequence_arg(active_usernames) self.emoji_status_custom_emoji_id = emoji_status_custom_emoji_id + self.has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled + self.has_hidden_members = has_hidden_members self._id_attrs = (self.id,) @@ -1372,6 +1398,7 @@ async def send_photo( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1408,6 +1435,7 @@ async def send_photo( connect_timeout=connect_timeout, pool_timeout=pool_timeout, api_kwargs=api_kwargs, + has_spoiler=has_spoiler, ) async def send_contact( @@ -1818,6 +1846,7 @@ async def send_animation( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1858,6 +1887,7 @@ async def send_animation( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def send_sticker( @@ -1977,6 +2007,7 @@ async def send_video( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2018,6 +2049,7 @@ async def send_video( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def send_video_note( @@ -2663,8 +2695,8 @@ async def create_forum_topic( async def edit_forum_topic( self, message_thread_id: int, - name: str, - icon_custom_emoji_id: str, + name: str = None, + icon_custom_emoji_id: str = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, diff --git a/telegram/_forumtopic.py b/telegram/_forumtopic.py index 38684118dac..6e1d67b5be2 100644 --- a/telegram/_forumtopic.py +++ b/telegram/_forumtopic.py @@ -132,3 +132,63 @@ class ForumTopicReopened(TelegramObject): """ __slots__ = () + + +class ForumTopicEdited(TelegramObject): + """ + This object represents a service message about an edited forum topic. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`name` and :attr:`icon_custom_emoji_id` are equal. + + .. versionadded:: 20.0 + + Args: + name (:obj:`str`, optional): New name of the topic, if it was edited. + icon_custom_emoji_id (:obj:`str`, optional): New identifier of the custom emoji shown as + the topic icon, if it was edited; an empty string if the icon was removed. + + Attributes: + name (:obj:`str`):Optional. New name of the topic, if it was edited. + icon_custom_emoji_id (:obj:`str`): Optional. New identifier of the custom emoji shown as + the topic icon, if it was edited; an empty string if the icon was removed. + """ + + __slots__ = ("name", "icon_custom_emoji_id") + + def __init__( + self, + name: str = None, + icon_custom_emoji_id: str = None, + *, + api_kwargs: JSONDict = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.name = name + self.icon_custom_emoji_id = icon_custom_emoji_id + + self._id_attrs = (self.name, self.icon_custom_emoji_id) + + self._freeze() + + +class GeneralForumTopicHidden(TelegramObject): + """ + This object represents a service message about General forum topic hidden in the chat. + Currently holds no information. + + .. versionadded:: 20.0 + """ + + __slots__ = () + + +class GeneralForumTopicUnhidden(TelegramObject): + """ + This object represents a service message about General forum topic unhidden in the chat. + Currently holds no information. + + .. versionadded:: 20.0 + """ + + __slots__ = () diff --git a/telegram/_message.py b/telegram/_message.py index 3f84539f4bf..2a0b96b7b38 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -36,7 +36,14 @@ from telegram._files.video import Video from telegram._files.videonote import VideoNote from telegram._files.voice import Voice -from telegram._forumtopic import ForumTopicClosed, ForumTopicCreated, ForumTopicReopened +from telegram._forumtopic import ( + ForumTopicClosed, + ForumTopicCreated, + ForumTopicEdited, + ForumTopicReopened, + GeneralForumTopicHidden, + GeneralForumTopicUnhidden, +) from telegram._games.game import Game from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged @@ -59,6 +66,7 @@ VideoChatStarted, ) from telegram._webappdata import WebAppData +from telegram._writeaccessallowed import WriteAccessAllowed from telegram.constants import MessageAttachmentType, ParseMode from telegram.helpers import escape_markdown @@ -279,15 +287,35 @@ class Message(TelegramObject): .. versionadded:: 20.0 forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message: - forum topic created + forum topic created. .. versionadded:: 20.0 forum_topic_closed (:class:`telegram.ForumTopicClosed`, optional): Service message: - forum topic closed + forum topic closed. .. versionadded:: 20.0 forum_topic_reopened (:class:`telegram.ForumTopicReopened`, optional): Service message: - forum topic reopened + forum topic reopened. + + .. versionadded:: 20.0 + forum_topic_edited (:class:`telegram.ForumTopicEdited`, optional): Service message: + forum topic edited. + + .. versionadded:: 20.0 + general_forum_topic_hidden (:class:`telegram.GeneralForumTopicHidden`, optional): + Service message: General forum topic hidden. + + .. versionadded:: 20.0 + general_forum_topic_unhidden (:class:`telegram.GeneralForumTopicUnhidden`, optional): + Service message: General forum topic unhidden. + + .. versionadded:: 20.0 + write_access_allowed (:class:`telegram.WriteAccessAllowed`, optional): Service message: + the user allowed the bot added to the attachment menu to write messages. + + .. versionadded:: 20.0 + has_media_spoiler (:obj:`bool`, optional): :obj:`True`, if the message media is covered + by a spoiler animation. .. versionadded:: 20.0 @@ -471,15 +499,35 @@ class Message(TelegramObject): .. versionadded:: 20.0 forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message: - forum topic created + forum topic created. .. versionadded:: 20.0 forum_topic_closed (:class:`telegram.ForumTopicClosed`): Optional. Service message: - forum topic closed + forum topic closed. .. versionadded:: 20.0 forum_topic_reopened (:class:`telegram.ForumTopicReopened`): Optional. Service message: - forum topic reopened + forum topic reopened. + + .. versionadded:: 20.0 + forum_topic_edited (:class:`telegram.ForumTopicEdited`): Optional. Service message: + forum topic edited. + + .. versionadded:: 20.0 + general_forum_topic_hidden (:class:`telegram.GeneralForumTopicHidden`): Optional. + Service message: General forum topic hidden. + + .. versionadded:: 20.0 + general_forum_topic_unhidden (:class:`telegram.GeneralForumTopicUnhidden`): Optional. + Service message: General forum topic unhidden. + + .. versionadded:: 20.0 + write_access_allowed (:class:`telegram.WriteAccessAllowed`): Optional. Service message: + the user allowed the bot added to the attachment menu to write messages. + + .. versionadded:: 20.0 + has_media_spoiler (:obj:`bool`): Optional. :obj:`True`, if the message media is covered + by a spoiler animation. .. versionadded:: 20.0 @@ -554,6 +602,11 @@ class Message(TelegramObject): "forum_topic_created", "forum_topic_closed", "forum_topic_reopened", + "forum_topic_edited", + "general_forum_topic_hidden", + "general_forum_topic_unhidden", + "write_access_allowed", + "has_media_spoiler", ) def __init__( @@ -622,6 +675,11 @@ def __init__( forum_topic_created: ForumTopicCreated = None, forum_topic_closed: ForumTopicClosed = None, forum_topic_reopened: ForumTopicReopened = None, + forum_topic_edited: ForumTopicEdited = None, + general_forum_topic_hidden: GeneralForumTopicHidden = None, + general_forum_topic_unhidden: GeneralForumTopicUnhidden = None, + write_access_allowed: WriteAccessAllowed = None, + has_media_spoiler: bool = None, *, api_kwargs: JSONDict = None, ): @@ -693,6 +751,11 @@ def __init__( self.forum_topic_created = forum_topic_created self.forum_topic_closed = forum_topic_closed self.forum_topic_reopened = forum_topic_reopened + self.forum_topic_edited = forum_topic_edited + self.general_forum_topic_hidden = general_forum_topic_hidden + self.general_forum_topic_unhidden = general_forum_topic_unhidden + self.write_access_allowed = write_access_allowed + self.has_media_spoiler = has_media_spoiler self._effective_attachment = DEFAULT_NONE @@ -792,6 +855,16 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Message"]: data["forum_topic_reopened"] = ForumTopicReopened.de_json( data.get("forum_topic_reopened"), bot ) + data["forum_topic_edited"] = ForumTopicEdited.de_json(data.get("forum_topic_edited"), bot) + data["general_forum_topic_hidden"] = GeneralForumTopicHidden.de_json( + data.get("general_forum_topic_hidden"), bot + ) + data["general_forum_topic_unhidden"] = GeneralForumTopicUnhidden.de_json( + data.get("general_forum_topic_unhidden"), bot + ) + data["write_access_allowed"] = WriteAccessAllowed.de_json( + data.get("write_access_allowed"), bot + ) return super().de_json(data=data, bot=bot) @@ -1188,6 +1261,7 @@ async def reply_photo( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, quote: bool = None, @@ -1231,6 +1305,7 @@ async def reply_photo( connect_timeout=connect_timeout, pool_timeout=pool_timeout, api_kwargs=api_kwargs, + has_spoiler=has_spoiler, ) async def reply_audio( @@ -1375,6 +1450,7 @@ async def reply_animation( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, quote: bool = None, @@ -1423,6 +1499,7 @@ async def reply_animation( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def reply_sticker( @@ -1491,6 +1568,7 @@ async def reply_video( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, quote: bool = None, @@ -1539,6 +1617,7 @@ async def reply_video( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def reply_video_note( @@ -2800,8 +2879,8 @@ async def unpin( async def edit_forum_topic( self, - name: str, - icon_custom_emoji_id: str, + name: str = None, + icon_custom_emoji_id: str = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, diff --git a/telegram/_replykeyboardmarkup.py b/telegram/_replykeyboardmarkup.py index 2a481c8e6a8..7050c24d6ea 100644 --- a/telegram/_replykeyboardmarkup.py +++ b/telegram/_replykeyboardmarkup.py @@ -68,6 +68,11 @@ class ReplyKeyboardMarkup(TelegramObject): characters. .. versionadded:: 13.7 + is_persistent (:obj:`bool`, optional): Requests clients to always show the keyboard when + the regular keyboard is hidden. Defaults to :obj:`False`, in which case the custom + keyboard can be hidden and opened with a keyboard icon. + + .. versionadded:: 20.0 Attributes: keyboard (Tuple[Tuple[:class:`telegram.KeyboardButton` | :obj:`str`]]): Array of button @@ -80,6 +85,10 @@ class ReplyKeyboardMarkup(TelegramObject): field when the reply is active. .. versionadded:: 13.7 + is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard when + the regular keyboard is hidden. + + .. versionadded:: 20.0 """ @@ -89,6 +98,7 @@ class ReplyKeyboardMarkup(TelegramObject): "resize_keyboard", "one_time_keyboard", "input_field_placeholder", + "is_persistent", ) def __init__( @@ -98,6 +108,7 @@ def __init__( one_time_keyboard: bool = None, selective: bool = None, input_field_placeholder: str = None, + is_persistent: bool = None, *, api_kwargs: JSONDict = None, ): @@ -119,6 +130,7 @@ def __init__( self.one_time_keyboard = one_time_keyboard self.selective = selective self.input_field_placeholder = input_field_placeholder + self.is_persistent = is_persistent self._id_attrs = (self.keyboard,) @@ -132,6 +144,7 @@ def from_button( one_time_keyboard: bool = False, selective: bool = False, input_field_placeholder: str = None, + is_persistent: bool = None, **kwargs: object, ) -> "ReplyKeyboardMarkup": """Shortcut for:: @@ -165,6 +178,10 @@ def from_button( field when the reply is active. .. versionadded:: 13.7 + is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard + when the regular keyboard is hidden. + + .. versionadded:: 20.0 """ return cls( [[button]], @@ -172,6 +189,7 @@ def from_button( one_time_keyboard=one_time_keyboard, selective=selective, input_field_placeholder=input_field_placeholder, + is_persistent=is_persistent, **kwargs, # type: ignore[arg-type] ) @@ -183,6 +201,7 @@ def from_row( one_time_keyboard: bool = False, selective: bool = False, input_field_placeholder: str = None, + is_persistent: bool = None, **kwargs: object, ) -> "ReplyKeyboardMarkup": """Shortcut for:: @@ -216,6 +235,10 @@ def from_row( field when the reply is active. .. versionadded:: 13.7 + is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard + when the regular keyboard is hidden. + + .. versionadded:: 20.0 """ return cls( @@ -224,6 +247,7 @@ def from_row( one_time_keyboard=one_time_keyboard, selective=selective, input_field_placeholder=input_field_placeholder, + is_persistent=is_persistent, **kwargs, # type: ignore[arg-type] ) @@ -235,6 +259,7 @@ def from_column( one_time_keyboard: bool = False, selective: bool = False, input_field_placeholder: str = None, + is_persistent: bool = None, **kwargs: object, ) -> "ReplyKeyboardMarkup": """Shortcut for:: @@ -268,6 +293,10 @@ def from_column( field when the reply is active. .. versionadded:: 13.7 + is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard + when the regular keyboard is hidden. + + .. versionadded:: 20.0 """ button_grid = [[button] for button in button_column] @@ -277,6 +306,7 @@ def from_column( one_time_keyboard=one_time_keyboard, selective=selective, input_field_placeholder=input_field_placeholder, + is_persistent=is_persistent, **kwargs, # type: ignore[arg-type] ) diff --git a/telegram/_user.py b/telegram/_user.py index ba0e2364757..19fec324b8d 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -427,6 +427,7 @@ async def send_photo( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -463,6 +464,7 @@ async def send_photo( connect_timeout=connect_timeout, pool_timeout=pool_timeout, api_kwargs=api_kwargs, + has_spoiler=has_spoiler, ) async def send_media_group( @@ -955,6 +957,7 @@ async def send_animation( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -995,6 +998,7 @@ async def send_animation( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def send_sticker( @@ -1056,6 +1060,7 @@ async def send_video( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1097,6 +1102,7 @@ async def send_video( filename=filename, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, ) async def send_venue( diff --git a/telegram/_writeaccessallowed.py b/telegram/_writeaccessallowed.py new file mode 100644 index 00000000000..5c951c9ecb3 --- /dev/null +++ b/telegram/_writeaccessallowed.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2022 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains objects related to the write access allowed service message.""" +from telegram._telegramobject import TelegramObject + + +class WriteAccessAllowed(TelegramObject): + """ + This object represents a service message about a user allowing a bot added to the attachment + menu to write messages. Currently holds no information. + + .. versionadded:: 20.0 + """ + + __slots__ = () diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 358e023e45c..4ee99e7b9f1 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -1266,8 +1266,8 @@ async def edit_forum_topic( self, chat_id: Union[str, int], message_thread_id: int, - name: str, - icon_custom_emoji_id: str, + name: str = None, + icon_custom_emoji_id: str = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2047,6 +2047,7 @@ async def send_animation( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2072,6 +2073,7 @@ async def send_animation( caption_entities=caption_entities, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, filename=filename, read_timeout=read_timeout, write_timeout=write_timeout, @@ -2134,6 +2136,7 @@ async def send_chat_action( self, chat_id: Union[str, int], action: str, + message_thread_id: int = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2145,6 +2148,7 @@ async def send_chat_action( return await super().send_chat_action( chat_id=chat_id, action=action, + message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -2521,6 +2525,7 @@ async def send_photo( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2542,6 +2547,7 @@ async def send_photo( caption_entities=caption_entities, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, filename=filename, read_timeout=read_timeout, write_timeout=write_timeout, @@ -2708,6 +2714,7 @@ async def send_video( caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None, protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: int = None, + has_spoiler: bool = None, *, filename: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2734,6 +2741,7 @@ async def send_video( caption_entities=caption_entities, protect_content=protect_content, message_thread_id=message_thread_id, + has_spoiler=has_spoiler, filename=filename, read_timeout=read_timeout, write_timeout=write_timeout, diff --git a/tests/test_animation.py b/tests/test_animation.py index 98c2184eaba..20fc34cc506 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -93,6 +93,7 @@ async def test_send_all_args(self, bot, chat_id, animation_file, animation, thum disable_notification=False, protect_content=True, thumb=thumb_file, + has_spoiler=True, ) assert isinstance(message.animation, Animation) @@ -106,6 +107,7 @@ async def test_send_all_args(self, bot, chat_id, animation_file, animation, thum assert message.animation.thumb.width == self.width assert message.animation.thumb.height == self.height assert message.has_protected_content + assert message.has_media_spoiler @pytest.mark.flaky(3, 1) async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): diff --git a/tests/test_bot.py b/tests/test_bot.py index 916461bccf0..d2c30209c5a 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -966,6 +966,18 @@ async def test_wrong_chat_action(self, bot, chat_id): with pytest.raises(BadRequest, match="Wrong parameter action"): await bot.send_chat_action(chat_id, "unknown action") + async def test_send_chat_action_all_args(self, bot, chat_id, provider_token, monkeypatch): + async def make_assertion(*args, **_): + kwargs = args[1] + return ( + kwargs["chat_id"] == chat_id + and kwargs["action"] == "action" + and kwargs["message_thread_id"] == 1 + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + assert await bot.send_chat_action(chat_id, "action", 1) + @pytest.mark.asyncio async def test_answer_web_app_query(self, bot, raw_bot, monkeypatch): params = False diff --git a/tests/test_chat.py b/tests/test_chat.py index a8418ec9f6b..06abde7bb3f 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -51,6 +51,8 @@ def chat(bot): is_forum=True, active_usernames=TestChat.active_usernames, emoji_status_custom_emoji_id=TestChat.emoji_status_custom_emoji_id, + has_aggressive_anti_spam_enabled=TestChat.has_aggressive_anti_spam_enabled, + has_hidden_members=TestChat.has_hidden_members, ) chat.set_bot(bot) chat._unfreeze() @@ -82,6 +84,8 @@ class TestChat: is_forum = True active_usernames = ["These", "Are", "Usernames!"] emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID" + has_aggressive_anti_spam_enabled = True + has_hidden_members = True def test_slot_behaviour(self, chat, mro_slots): for attr in chat.__slots__: @@ -112,6 +116,8 @@ def test_de_json(self, bot): "is_forum": self.is_forum, "active_usernames": self.active_usernames, "emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id, + "has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled, + "has_hidden_members": self.has_hidden_members, } chat = Chat.de_json(json_dict, bot) @@ -141,6 +147,8 @@ def test_de_json(self, bot): assert chat.is_forum == self.is_forum assert chat.active_usernames == tuple(self.active_usernames) assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id + assert chat.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled + assert chat.has_hidden_members == self.has_hidden_members def test_to_dict(self, chat): chat_dict = chat.to_dict() @@ -166,6 +174,10 @@ def test_to_dict(self, chat): assert chat_dict["is_forum"] == chat.is_forum assert chat_dict["active_usernames"] == list(chat.active_usernames) assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id + assert ( + chat_dict["has_aggressive_anti_spam_enabled"] == chat.has_aggressive_anti_spam_enabled + ) + assert chat_dict["has_hidden_members"] == chat.has_hidden_members def test_always_tuples_attributes(self): chat = Chat( diff --git a/tests/test_forum.py b/tests/test_forum.py index d903d3e3d1a..068c0b2f9ad 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -18,7 +18,16 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import pytest -from telegram import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened, Sticker +from telegram import ( + ForumTopic, + ForumTopicClosed, + ForumTopicCreated, + ForumTopicEdited, + ForumTopicReopened, + GeneralForumTopicHidden, + GeneralForumTopicUnhidden, + Sticker, +) TEST_MSG_TEXT = "Topics are forever" TEST_TOPIC_ICON_COLOR = 0x6FB9F0 @@ -333,3 +342,95 @@ def test_to_dict(self): action = ForumTopicReopened() action_dict = action.to_dict() assert action_dict == {} + + +@pytest.fixture +def topic_edited(emoji_id): + return ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id=emoji_id) + + +class TestForumTopicEdited: + def test_slot_behaviour(self, topic_edited, mro_slots): + for attr in topic_edited.__slots__: + assert getattr(topic_edited, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(topic_edited)) == len(set(mro_slots(topic_edited))), "duplicate slot" + + def test_expected_values(self, topic_edited, emoji_id): + assert topic_edited.name == TEST_TOPIC_NAME + assert topic_edited.icon_custom_emoji_id == emoji_id + + def test_de_json(self, bot, emoji_id): + assert ForumTopicEdited.de_json(None, bot=bot) is None + + json_dict = {"name": TEST_TOPIC_NAME, "icon_custom_emoji_id": emoji_id} + action = ForumTopicEdited.de_json(json_dict, bot) + assert action.api_kwargs == {} + + assert action.name == TEST_TOPIC_NAME + assert action.icon_custom_emoji_id == emoji_id + # special test since it is mentioned in the docs that icon_custom_emoji_id can be an + # empty string + json_dict = {"icon_custom_emoji_id": ""} + action = ForumTopicEdited.de_json(json_dict, bot) + assert action.icon_custom_emoji_id == "" + + def test_to_dict(self, topic_edited, emoji_id): + action_dict = topic_edited.to_dict() + + assert isinstance(action_dict, dict) + assert action_dict["name"] == TEST_TOPIC_NAME + assert action_dict["icon_custom_emoji_id"] == emoji_id + + def test_equality(self, emoji_id): + a = ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id="") + b = ForumTopicEdited( + name=TEST_TOPIC_NAME, + icon_custom_emoji_id="", + ) + c = ForumTopicEdited(name=f"{TEST_TOPIC_NAME}!", icon_custom_emoji_id=emoji_id) + d = ForumTopicEdited(icon_custom_emoji_id="") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + +class TestGeneralForumTopicHidden: + def test_slot_behaviour(self, mro_slots): + action = GeneralForumTopicHidden() + for attr in action.__slots__: + assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" + + def test_de_json(self): + action = GeneralForumTopicHidden.de_json({}, None) + assert action.api_kwargs == {} + assert isinstance(action, GeneralForumTopicHidden) + + def test_to_dict(self): + action = GeneralForumTopicHidden() + action_dict = action.to_dict() + assert action_dict == {} + + +class TestGeneralForumTopicUnhidden: + def test_slot_behaviour(self, mro_slots): + action = GeneralForumTopicUnhidden() + for attr in action.__slots__: + assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" + + def test_de_json(self): + action = GeneralForumTopicUnhidden.de_json({}, None) + assert action.api_kwargs == {} + assert isinstance(action, GeneralForumTopicUnhidden) + + def test_to_dict(self): + action = GeneralForumTopicUnhidden() + action_dict = action.to_dict() + assert action_dict == {} diff --git a/tests/test_photo.py b/tests/test_photo.py index 4d7baeeec48..5238cf1c3b6 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -107,6 +107,7 @@ async def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo) disable_notification=False, protect_content=True, parse_mode="Markdown", + has_spoiler=True, ) assert isinstance(message.photo[-2], PhotoSize) @@ -123,6 +124,7 @@ async def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo) assert message.caption == TestPhoto.caption.replace("*", "") assert message.has_protected_content + assert message.has_media_spoiler @pytest.mark.flaky(3, 1) async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch): diff --git a/tests/test_replykeyboardmarkup.py b/tests/test_replykeyboardmarkup.py index 56b1366e016..7f7a67159af 100644 --- a/tests/test_replykeyboardmarkup.py +++ b/tests/test_replykeyboardmarkup.py @@ -29,6 +29,7 @@ def reply_keyboard_markup(): resize_keyboard=TestReplyKeyboardMarkup.resize_keyboard, one_time_keyboard=TestReplyKeyboardMarkup.one_time_keyboard, selective=TestReplyKeyboardMarkup.selective, + is_persistent=TestReplyKeyboardMarkup.is_persistent, ) @@ -37,6 +38,7 @@ class TestReplyKeyboardMarkup: resize_keyboard = True one_time_keyboard = True selective = True + is_persistent = True def test_slot_behaviour(self, reply_keyboard_markup, mro_slots): inst = reply_keyboard_markup @@ -103,6 +105,7 @@ def test_expected_values(self, reply_keyboard_markup): assert reply_keyboard_markup.resize_keyboard == self.resize_keyboard assert reply_keyboard_markup.one_time_keyboard == self.one_time_keyboard assert reply_keyboard_markup.selective == self.selective + assert reply_keyboard_markup.is_persistent == self.is_persistent def test_wrong_keyboard_inputs(self): with pytest.raises(ValueError): @@ -134,6 +137,7 @@ def test_to_dict(self, reply_keyboard_markup): == reply_keyboard_markup.one_time_keyboard ) assert reply_keyboard_markup_dict["selective"] == reply_keyboard_markup.selective + assert reply_keyboard_markup_dict["is_persistent"] == reply_keyboard_markup.is_persistent def test_equality(self): a = ReplyKeyboardMarkup.from_column(["button1", "button2", "button3"]) diff --git a/tests/test_video.py b/tests/test_video.py index 80f68cd57bd..3c59c17ee09 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -105,6 +105,7 @@ async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): height=video.height, parse_mode="Markdown", thumb=thumb_file, + has_spoiler=True, ) assert isinstance(message.video, Video) @@ -125,6 +126,7 @@ async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): assert message.video.file_name == self.file_name assert message.has_protected_content + assert message.has_media_spoiler @pytest.mark.flaky(3, 1) async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch): diff --git a/tests/test_writeaccessallowed.py b/tests/test_writeaccessallowed.py new file mode 100644 index 00000000000..35cc2dcbbb4 --- /dev/null +++ b/tests/test_writeaccessallowed.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2022 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +from telegram import WriteAccessAllowed + + +def test_slot_behaviour(mro_slots): + action = WriteAccessAllowed() + for attr in action.__slots__: + assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" + + +def test_de_json(): + action = WriteAccessAllowed.de_json({}, None) + assert action.api_kwargs == {} + assert isinstance(action, WriteAccessAllowed) + + +def test_to_dict(): + action = WriteAccessAllowed() + action_dict = action.to_dict() + assert action_dict == {} From 9b1bb6bf810a0175cff93770714ff3c2fc37fc02 Mon Sep 17 00:00:00 2001 From: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:04:17 +0300 Subject: [PATCH 02/23] add general forum topic methods to `Bot` and shortcuts (no tests yet) --- telegram/_bot.py | 231 ++++++++++++++++++++++++++++++++++++++++++ telegram/_chat.py | 168 ++++++++++++++++++++++++++++++ telegram/_message.py | 172 +++++++++++++++++++++++++++++++ telegram/constants.py | 24 +++-- 4 files changed, 587 insertions(+), 8 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 1a1f7626423..7bf1b85a190 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -7141,6 +7141,227 @@ async def unpin_all_forum_topic_messages( api_kwargs=api_kwargs, ) + @_log + async def edit_general_forum_topic( + self, + chat_id: Union[str, int], + name: str, + *, + 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, + ) -> bool: + """ + Use this method to edit the name of the 'General' topic in a forum supergroup chat. The bot + must be an administrator in the chat for this to work and must have + :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + + .. seealso:: :meth:`telegram.Message.edit_general_forum_topic`, + :meth:`telegram.Chat.edit_general_forum_topic`, + + .. versionadded:: 20.0 + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + name (:obj:`str`): New topic name, + :tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`- + :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"chat_id": chat_id, "name": name} + + return await self._post( + "editGeneralForumTopic", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + @_log + async def close_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + ) -> bool: + """ + Use this method to close an open 'General' topic in a forum supergroup chat. The bot must + be an administrator in the chat for this to work and must have + :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + + .. seealso:: :meth:`telegram.Message.close_general_forum_topic`, + :meth:`telegram.Chat.close_general_forum_topic`, + + .. versionadded:: 20.0 + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"chat_id": chat_id} + + return await self._post( + "closeGeneralForumTopic", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + @_log + async def reopen_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + ) -> bool: + """ + Use this method to reopen a closed 'General' topic in a forum supergroup chat. The bot must + be an administrator in the chat for this to work and must have + :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + The topic will be automatically unhidden if it was hidden. + + .. seealso:: :meth:`telegram.Message.reopen_general_forum_topic`, + :meth:`telegram.Chat.reopen_general_forum_topic`, + + .. versionadded:: 20.0 + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"chat_id": chat_id} + + return await self._post( + "reopenGeneralForumTopic", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + @_log + async def hide_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + ) -> bool: + """ + Use this method to hide the 'General' topic in a forum supergroup chat. The bot must + be an administrator in the chat for this to work and must have + :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + The topic will be automatically closed if it was open. + + .. seealso:: :meth:`telegram.Message.hide_general_forum_topic`, + :meth:`telegram.Chat.hide_general_forum_topic`, + + .. versionadded:: 20.0 + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"chat_id": chat_id} + + return await self._post( + "hideGeneralForumTopic", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + @_log + async def unhide_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + ) -> bool: + """ + Use this method to unhide the 'General' topic in a forum supergroup chat. The bot must + be an administrator in the chat for this to work and must have + :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + + .. seealso:: :meth:`telegram.Message.unhide_general_forum_topic`, + :meth:`telegram.Chat.unhide_general_forum_topic`, + + .. versionadded:: 20.0 + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"chat_id": chat_id} + + return await self._post( + "unhideGeneralForumTopic", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + def to_dict(self, recursive: bool = True) -> JSONDict: # skipcq: PYL-W0613 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -7349,3 +7570,13 @@ def __hash__(self) -> int: """Alias for :meth:`delete_forum_topic`""" unpinAllForumTopicMessages = unpin_all_forum_topic_messages """Alias for :meth:`unpin_all_forum_topic_messages`""" + editGeneralForumTopic = edit_general_forum_topic + """Alias for :meth:`edit_general_forum_topic`""" + closeGeneralForumTopic = close_general_forum_topic + """Alias for :meth:`close_general_forum_topic`""" + reopenGeneralForumTopic = reopen_general_forum_topic + """Alias for :meth:`reopen_general_forum_topic`""" + hideGeneralForumTopic = hide_general_forum_topic + """Alias for :meth:`hide_general_forum_topic`""" + unhideGeneralForumTopic = unhide_general_forum_topic + """Alias for :meth:`unhide_general_forum_topic`""" diff --git a/telegram/_chat.py b/telegram/_chat.py index fa52dbdaae5..b93769e3f5e 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -2857,6 +2857,174 @@ async def unpin_all_forum_topic_messages( api_kwargs=api_kwargs, ) + async def edit_general_forum_topic( + self, + message_thread_id: int, + name: str = 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, + ) -> bool: + """Shortcut for:: + + await bot.edit_general_forum_topic( + chat_id=update.effective_chat.id, *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.edit_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().edit_general_forum_topic( + chat_id=self.id, + message_thread_id=message_thread_id, + name=name, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def close_general_forum_topic( + self, + message_thread_id: int, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.close_general_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.close_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().close_general_forum_topic( + chat_id=self.id, + message_thread_id=message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def reopen_general_forum_topic( + self, + message_thread_id: int, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.reopen_general_forum_topic( + chat_id=update.effective_chat.id, *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.reopen_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().reopen_general_forum_topic( + chat_id=self.id, + message_thread_id=message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def hide_general_forum_topic( + self, + message_thread_id: int, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.hide_general_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.hide_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().hide_general_forum_topic( + chat_id=self.id, + message_thread_id=message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def unhide_general_forum_topic( + self, + message_thread_id: int, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.unhide_general_forum_topic ( + chat_id=update.effective_chat.id, *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.unhide_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().unhide_general_forum_topic( + chat_id=self.id, + message_thread_id=message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def get_menu_button( self, *, diff --git a/telegram/_message.py b/telegram/_message.py index 2a0b96b7b38..3bd318afc08 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -3051,6 +3051,178 @@ async def unpin_all_forum_topic_messages( api_kwargs=api_kwargs, ) + async def edit_general_forum_topic( + self, + name: str = 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, + ) -> bool: + """Shortcut for:: + + await bot.edit_general_forum_topic( + chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.edit_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().edit_general_forum_topic( + chat_id=self.chat_id, + message_thread_id=self.message_thread_id, + name=name, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def close_general_forum_topic( + self, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.close_general_forum_topic( + chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.close_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().close_general_forum_topic( + chat_id=self.chat_id, + message_thread_id=self.message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def reopen_general_forum_topic( + self, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.reopen_general_forum_topic( + chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.reopen_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().reopen_general_forum_topic( + chat_id=self.chat_id, + message_thread_id=self.message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def hide_general_forum_topic( + self, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.hide_general_forum_topic( + chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.hide_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().hide_general_forum_topic( + chat_id=self.chat_id, + message_thread_id=self.message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def unhide_general_forum_topic( + self, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.unhide_general_forum_topic( + chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.unhide_general_forum_topic`. + + .. versionadded:: 20.0 + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().unhide_general_forum_topic( + chat_id=self.chat_id, + message_thread_id=self.message_thread_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + def parse_entity(self, entity: MessageEntity) -> str: """Returns the text from a given :class:`telegram.MessageEntity`. diff --git a/telegram/constants.py b/telegram/constants.py index cf3f033ad1d..d2d93b999a3 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -1537,14 +1537,22 @@ class ForumTopicLimit(IntEnum): __slots__ = () MIN_NAME_LENGTH = 1 - """:obj:`int`: Minimum length of a :obj:`str` passed as the - :paramref:`~telegram.Bot.create_forum_topic.name` parameter of - :meth:`telegram.Bot.create_forum_topic` and :paramref:`~telegram.Bot.edit_forum_topic.name` - parameter of :meth:`telegram.Bot.edit_forum_topic`. + """:obj:`int`: Minimum length of a :obj:`str` passed as: + + * :paramref:`~telegram.Bot.create_forum_topic.name` parameter of + :meth:`telegram.Bot.create_forum_topic` + * :paramref:`~telegram.Bot.edit_forum_topic.name` parameter of + :meth:`telegram.Bot.edit_forum_topic` + * :paramref:`~telegram.Bot.edit_general_forum_topic.name` parameter of + :meth:`telegram.Bot.edit_general_forum_topic` """ MAX_NAME_LENGTH = 128 - """:obj:`int`: Maximum length of a :obj:`str` passed as the - :paramref:`~telegram.Bot.create_forum_topic.name` parameter of - :meth:`telegram.Bot.create_forum_topic` and :paramref:`~telegram.Bot.edit_forum_topic.name` - parameter of :meth:`telegram.Bot.edit_forum_topic`. + """:obj:`int`: Maximum length of a :obj:`str` passed as: + + * :paramref:`~telegram.Bot.create_forum_topic.name` parameter of + :meth:`telegram.Bot.create_forum_topic` + * :paramref:`~telegram.Bot.edit_forum_topic.name` parameter of + :meth:`telegram.Bot.edit_forum_topic` + * :paramref:`~telegram.Bot.edit_general_forum_topic.name` parameter of + :meth:`telegram.Bot.edit_general_forum_topic` """ From 4d4ed91860bc58c5003edb9bbaecbcb1e2f6fb30 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 30 Dec 2022 15:52:59 +0100 Subject: [PATCH 03/23] Feat: Test methods + shortcuts Also fix some small stuff --- docs/substitutions/global.rst | 4 +- telegram/_bot.py | 12 ++-- telegram/_chat.py | 12 +--- telegram/_message.py | 17 ++---- telegram/ext/_extbot.py | 107 ++++++++++++++++++++++++++++++++++ tests/test_bot.py | 2 +- tests/test_chat.py | 105 +++++++++++++++++++++++++++++++++ tests/test_forum.py | 38 ++++++++++++ tests/test_message.py | 105 +++++++++++++++++++++++++++++++++ 9 files changed, 372 insertions(+), 30 deletions(-) diff --git a/docs/substitutions/global.rst b/docs/substitutions/global.rst index 579bc903d5e..9d835e6c783 100644 --- a/docs/substitutions/global.rst +++ b/docs/substitutions/global.rst @@ -46,6 +46,4 @@ .. |tupleclassattrs| replace:: This attribute is now an immutable tuple. -.. |alwaystuple| replace:: This attribute is now always a tuple, that may be empty. - -.. |has_spoiler| replace:: Pass :obj:`True` if the photo needs to be covered with a spoiler animation. \ No newline at end of file +.. |alwaystuple| replace:: This attribute is now always a tuple, that may be empty. \ No newline at end of file diff --git a/telegram/_bot.py b/telegram/_bot.py index 8bf0778c78a..61f70441d67 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -963,7 +963,8 @@ async def send_photo( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the photo needs to be covered + with a spoiler animation. .. versionadded:: 20.0 @@ -1417,7 +1418,8 @@ async def send_video( .. versionchanged:: 20.0 File paths as input is also accepted for bots *not* running in :paramref:`~telegram.Bot.local_mode`. - has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the video needs to be covered + with a spoiler animation. .. versionadded:: 20.0 @@ -1665,7 +1667,8 @@ async def send_animation( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - has_spoiler (:obj:`int`, optional): |message_thread_id_arg| + has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the animation needs to be + covered with a spoiler animation. .. versionadded:: 20.0 @@ -3876,7 +3879,8 @@ async def get_chat_member( pool_timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None, ) -> ChatMember: - """Use this method to get information about a member of a chat. + """Use this method to get information about a member of a chat. The method is guaranteed + to work only if the bot is an administrator in the chat. .. seealso:: :meth:`telegram.Chat.get_member` diff --git a/telegram/_chat.py b/telegram/_chat.py index b93769e3f5e..3d3f89016df 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -2859,8 +2859,7 @@ async def unpin_all_forum_topic_messages( async def edit_general_forum_topic( self, - message_thread_id: int, - name: str = None, + name: str, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2884,7 +2883,6 @@ async def edit_general_forum_topic( """ return await self.get_bot().edit_general_forum_topic( chat_id=self.id, - message_thread_id=message_thread_id, name=name, read_timeout=read_timeout, write_timeout=write_timeout, @@ -2895,7 +2893,6 @@ async def edit_general_forum_topic( async def close_general_forum_topic( self, - message_thread_id: int, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2917,7 +2914,6 @@ async def close_general_forum_topic( """ return await self.get_bot().close_general_forum_topic( chat_id=self.id, - message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -2927,7 +2923,6 @@ async def close_general_forum_topic( async def reopen_general_forum_topic( self, - message_thread_id: int, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2951,7 +2946,6 @@ async def reopen_general_forum_topic( """ return await self.get_bot().reopen_general_forum_topic( chat_id=self.id, - message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -2961,7 +2955,6 @@ async def reopen_general_forum_topic( async def hide_general_forum_topic( self, - message_thread_id: int, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2983,7 +2976,6 @@ async def hide_general_forum_topic( """ return await self.get_bot().hide_general_forum_topic( chat_id=self.id, - message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -2993,7 +2985,6 @@ async def hide_general_forum_topic( async def unhide_general_forum_topic( self, - message_thread_id: int, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3017,7 +3008,6 @@ async def unhide_general_forum_topic( """ return await self.get_bot().unhide_general_forum_topic( chat_id=self.id, - message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/telegram/_message.py b/telegram/_message.py index 3bd318afc08..f32773a57e1 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -3053,7 +3053,7 @@ async def unpin_all_forum_topic_messages( async def edit_general_forum_topic( self, - name: str = None, + name: str, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3064,7 +3064,7 @@ async def edit_general_forum_topic( """Shortcut for:: await bot.edit_general_forum_topic( - chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + chat_id=message.chat_id, *args, **kwargs ) @@ -3078,7 +3078,6 @@ async def edit_general_forum_topic( """ return await self.get_bot().edit_general_forum_topic( chat_id=self.chat_id, - message_thread_id=self.message_thread_id, name=name, read_timeout=read_timeout, write_timeout=write_timeout, @@ -3099,7 +3098,7 @@ async def close_general_forum_topic( """Shortcut for:: await bot.close_general_forum_topic( - chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + chat_id=message.chat_id, *args, **kwargs ) @@ -3113,7 +3112,6 @@ async def close_general_forum_topic( """ return await self.get_bot().close_general_forum_topic( chat_id=self.chat_id, - message_thread_id=self.message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -3133,7 +3131,7 @@ async def reopen_general_forum_topic( """Shortcut for:: await bot.reopen_general_forum_topic( - chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + chat_id=message.chat_id, *args, **kwargs ) @@ -3147,7 +3145,6 @@ async def reopen_general_forum_topic( """ return await self.get_bot().reopen_general_forum_topic( chat_id=self.chat_id, - message_thread_id=self.message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -3167,7 +3164,7 @@ async def hide_general_forum_topic( """Shortcut for:: await bot.hide_general_forum_topic( - chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + chat_id=message.chat_id, *args, **kwargs ) @@ -3181,7 +3178,6 @@ async def hide_general_forum_topic( """ return await self.get_bot().hide_general_forum_topic( chat_id=self.chat_id, - message_thread_id=self.message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -3201,7 +3197,7 @@ async def unhide_general_forum_topic( """Shortcut for:: await bot.unhide_general_forum_topic( - chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args, + chat_id=message.chat_id, *args, **kwargs ) @@ -3215,7 +3211,6 @@ async def unhide_general_forum_topic( """ return await self.get_bot().unhide_general_forum_topic( chat_id=self.chat_id, - message_thread_id=self.message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 4ee99e7b9f1..b6cc99931a5 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -1288,6 +1288,28 @@ async def edit_forum_topic( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def edit_general_forum_topic( + self, + chat_id: Union[str, int], + name: str = 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, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().edit_general_forum_topic( + chat_id=chat_id, + name=name, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def edit_message_caption( self, chat_id: Union[str, int] = None, @@ -1864,6 +1886,26 @@ async def close_forum_topic( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def close_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().close_general_forum_topic( + chat_id=chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def create_forum_topic( self, chat_id: Union[str, int], @@ -1890,6 +1932,66 @@ async def create_forum_topic( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def reopen_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().reopen_general_forum_topic( + chat_id=chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def hide_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().hide_general_forum_topic( + chat_id=chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def unhide_general_forum_topic( + self, + chat_id: Union[str, int], + *, + 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, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().unhide_general_forum_topic( + chat_id=chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def pin_chat_message( self, chat_id: Union[str, int], @@ -3424,3 +3526,8 @@ async def upload_sticker_file( reopenForumTopic = reopen_forum_topic deleteForumTopic = delete_forum_topic unpinAllForumTopicMessages = unpin_all_forum_topic_messages + editGeneralForumTopic = edit_general_forum_topic + closeGeneralForumTopic = close_general_forum_topic + reopenGeneralForumTopic = reopen_general_forum_topic + hideGeneralForumTopic = hide_general_forum_topic + unhideGeneralForumTopic = unhide_general_forum_topic diff --git a/tests/test_bot.py b/tests/test_bot.py index d2c30209c5a..7fcb383a637 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2536,7 +2536,7 @@ async def test_pin_and_unpin_message(self, bot, super_group_id): # set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers # are tested in the test_sticker module. - # get_forum_topic_icon_stickers, edit_forum_topic, etc... + # get_forum_topic_icon_stickers, edit_forum_topic, general_forum etc... # are tested in the test_forum module. async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id): diff --git a/tests/test_chat.py b/tests/test_chat.py index 06abde7bb3f..bd8d0661d30 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -1044,6 +1044,111 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion) assert await chat.unpin_all_forum_topic_messages(message_thread_id=42) + async def test_edit_general_forum_topic(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == chat.id and kwargs["name"] == "WhatAName" + + assert check_shortcut_signature( + Chat.edit_general_forum_topic, + Bot.edit_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + chat.edit_general_forum_topic, + chat.get_bot(), + "edit_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.edit_general_forum_topic, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "edit_general_forum_topic", make_assertion) + assert await chat.edit_general_forum_topic(name="WhatAName") + + async def test_close_general_forum_topic(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == chat.id + + assert check_shortcut_signature( + Chat.close_general_forum_topic, + Bot.close_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + chat.close_general_forum_topic, + chat.get_bot(), + "close_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.close_general_forum_topic, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "close_general_forum_topic", make_assertion) + assert await chat.close_general_forum_topic() + + async def test_reopen_general_forum_topic(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == chat.id + + assert check_shortcut_signature( + Chat.reopen_general_forum_topic, + Bot.reopen_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + chat.reopen_general_forum_topic, + chat.get_bot(), + "reopen_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.reopen_general_forum_topic, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "reopen_general_forum_topic", make_assertion) + assert await chat.reopen_general_forum_topic() + + async def test_hide_general_forum_topic(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == chat.id + + assert check_shortcut_signature( + Chat.hide_general_forum_topic, + Bot.hide_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + chat.hide_general_forum_topic, + chat.get_bot(), + "hide_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.hide_general_forum_topic, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "hide_general_forum_topic", make_assertion) + assert await chat.hide_general_forum_topic() + + async def test_unhide_general_forum_topic(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == chat.id + + assert check_shortcut_signature( + Chat.unhide_general_forum_topic, + Bot.unhide_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + chat.unhide_general_forum_topic, + chat.get_bot(), + "unhide_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.unhide_general_forum_topic, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "unhide_general_forum_topic", make_assertion) + assert await chat.unhide_general_forum_topic() + def test_mention_html(self): with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): chat = Chat(id=1, type="foo") diff --git a/tests/test_forum.py b/tests/test_forum.py index 068c0b2f9ad..defd4f32d54 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -148,6 +148,8 @@ def test_equality(self, emoji_id, forum_group_id): assert a != e assert hash(a) != hash(e) + +class TestForumMethods: @pytest.mark.flaky(3, 1) async def test_create_forum_topic(self, real_topic): result = real_topic @@ -253,6 +255,42 @@ async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_to ) assert result is True, "Failed to unpin all the messages in forum topic" + async def test_edit_general_forum_topic(self, bot, forum_group_id): + result = await bot.edit_general_forum_topic( + chat_id=forum_group_id, + name="GENERAL_EDITED", + ) + assert result is True, "Failed to edit general forum topic" + # no way of checking the edited name, just the boolean result + + async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): + + result = await bot.close_general_forum_topic( + chat_id=forum_group_id, + ) + assert result is True, "Failed to close general forum topic" + # bot will still be able to send a message to a closed topic, so can't test anything like + # the inability to post to the topic + + result = await bot.reopen_general_forum_topic( + chat_id=forum_group_id, + ) + assert result is True, "Failed to reopen general forum topic" + + async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): + + result = await bot.hide_general_forum_topic( + chat_id=forum_group_id, + ) + assert result is True, "Failed to hide general forum topic" + # bot will still be able to send a message to a closed topic, so can't test anything like + # the inability to post to the topic + + result = await bot.unhide_general_forum_topic( + chat_id=forum_group_id, + ) + assert result is True, "Failed to unhide general forum topic" + @pytest.fixture def topic_created(): diff --git a/tests/test_message.py b/tests/test_message.py index 7891d1e43ea..f2f84465adc 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -1821,6 +1821,111 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(message.get_bot(), "unpin_all_forum_topic_messages", make_assertion) assert await message.unpin_all_forum_topic_messages() + async def test_edit_general_forum_topic(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == message.chat_id and kwargs["name"] == "WhatAName" + + assert check_shortcut_signature( + Message.edit_general_forum_topic, + Bot.edit_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + message.edit_general_forum_topic, + message.get_bot(), + "edit_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(message.edit_general_forum_topic, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "edit_general_forum_topic", make_assertion) + assert await message.edit_general_forum_topic(name="WhatAName") + + async def test_close_general_forum_topic(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == message.chat_id + + assert check_shortcut_signature( + Message.close_general_forum_topic, + Bot.close_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + message.close_general_forum_topic, + message.get_bot(), + "close_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(message.close_general_forum_topic, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "close_general_forum_topic", make_assertion) + assert await message.close_general_forum_topic() + + async def test_reopen_general_forum_topic(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == message.chat_id + + assert check_shortcut_signature( + Message.reopen_general_forum_topic, + Bot.reopen_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + message.reopen_general_forum_topic, + message.get_bot(), + "reopen_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(message.reopen_general_forum_topic, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "reopen_general_forum_topic", make_assertion) + assert await message.reopen_general_forum_topic() + + async def test_hide_general_forum_topic(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == message.chat_id + + assert check_shortcut_signature( + Message.hide_general_forum_topic, + Bot.hide_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + message.hide_general_forum_topic, + message.get_bot(), + "hide_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(message.hide_general_forum_topic, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "hide_general_forum_topic", make_assertion) + assert await message.hide_general_forum_topic() + + async def test_unhide_general_forum_topic(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return kwargs["chat_id"] == message.chat_id + + assert check_shortcut_signature( + Message.unhide_general_forum_topic, + Bot.unhide_general_forum_topic, + ["chat_id"], + [], + ) + assert await check_shortcut_call( + message.unhide_general_forum_topic, + message.get_bot(), + "unhide_general_forum_topic", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(message.unhide_general_forum_topic, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "unhide_general_forum_topic", make_assertion) + assert await message.unhide_general_forum_topic() + def test_equality(self): id_ = 1 a = Message( From c5c8d1e30f4e53d13e392cdef4e3d1c68afa0ccf Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 30 Dec 2022 19:20:42 +0400 Subject: [PATCH 04/23] add the attribute has_spoiler to `InputMedia*` classes Also add their tests --- telegram/_files/inputmedia.py | 26 ++++++++++++++++++++++---- tests/test_inputmedia.py | 12 ++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index bf40b1ea21c..3f9f9a120c8 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -144,6 +144,8 @@ class InputMediaAnimation(InputMedia): width (:obj:`int`, optional): Animation width. height (:obj:`int`, optional): Animation height. duration (:obj:`int`, optional): Animation duration in seconds. + has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the animation needs to be covered + with a spoiler animation. Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`. @@ -161,10 +163,12 @@ class InputMediaAnimation(InputMedia): width (:obj:`int`): Optional. Animation width. height (:obj:`int`): Optional. Animation height. duration (:obj:`int`): Optional. Animation duration in seconds. + has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the animation is covered with a + spoiler animation. """ - __slots__ = ("duration", "height", "thumb", "width") + __slots__ = ("duration", "height", "thumb", "width", "has_spoiler") def __init__( self, @@ -177,6 +181,7 @@ def __init__( duration: int = None, caption_entities: Sequence[MessageEntity] = None, filename: str = None, + has_spoiler: bool = None, *, api_kwargs: JSONDict = None, ): @@ -203,6 +208,7 @@ def __init__( self.width = width self.height = height self.duration = duration + self.has_spoiler = has_spoiler class InputMediaPhoto(InputMedia): @@ -228,6 +234,8 @@ class InputMediaPhoto(InputMedia): .. versionchanged:: 20.0 |sequenceclassargs| + has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the photo needs to be covered + with a spoiler animation. Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`. @@ -240,10 +248,12 @@ class InputMediaPhoto(InputMedia): * |tupleclassattrs| * |alwaystuple| + has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the photo is covered with a + spoiler animation. """ - __slots__ = () + __slots__ = ("has_spoiler",) def __init__( self, @@ -252,6 +262,7 @@ def __init__( parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Sequence[MessageEntity] = None, filename: str = None, + has_spoiler: bool = None, *, api_kwargs: JSONDict = None, ): @@ -267,7 +278,8 @@ def __init__( api_kwargs=api_kwargs, ) - self._freeze() + with self._unfrozen(): + self.has_spoiler = has_spoiler class InputMediaVideo(InputMedia): @@ -312,6 +324,8 @@ class InputMediaVideo(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. + has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered + with a spoiler animation. Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`. @@ -330,10 +344,12 @@ class InputMediaVideo(InputMedia): supports_streaming (:obj:`bool`): Optional. Pass :obj:`True`, if the uploaded video is suitable for streaming. thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send. + has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a + spoiler animation. """ - __slots__ = ("duration", "height", "thumb", "supports_streaming", "width") + __slots__ = ("duration", "height", "thumb", "supports_streaming", "width", "has_spoiler") def __init__( self, @@ -347,6 +363,7 @@ def __init__( thumb: FileInput = None, caption_entities: Sequence[MessageEntity] = None, filename: str = None, + has_spoiler: bool = None, *, api_kwargs: JSONDict = None, ): @@ -375,6 +392,7 @@ def __init__( self.duration = duration self.thumb = self._parse_thumb_input(thumb) self.supports_streaming = supports_streaming + self.has_spoiler = has_spoiler class InputMediaAudio(InputMedia): diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index d7f0e96789d..b88e1075c2c 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -67,6 +67,7 @@ def input_media_video(class_thumb_file): caption_entities=TestInputMediaVideo.caption_entities, thumb=class_thumb_file, supports_streaming=TestInputMediaVideo.supports_streaming, + has_spoiler=TestInputMediaVideo.has_spoiler, ) @@ -77,6 +78,7 @@ def input_media_photo(class_thumb_file): caption=TestInputMediaPhoto.caption, parse_mode=TestInputMediaPhoto.parse_mode, caption_entities=TestInputMediaPhoto.caption_entities, + has_spoiler=TestInputMediaPhoto.has_spoiler, ) @@ -91,6 +93,7 @@ def input_media_animation(class_thumb_file): height=TestInputMediaAnimation.height, thumb=class_thumb_file, duration=TestInputMediaAnimation.duration, + has_spoiler=TestInputMediaAnimation.has_spoiler, ) @@ -130,6 +133,7 @@ class TestInputMediaVideo: parse_mode = "HTML" supports_streaming = True caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] + has_spoiler = True def test_slot_behaviour(self, input_media_video, mro_slots): inst = input_media_video @@ -148,6 +152,7 @@ def test_expected_values(self, input_media_video): assert input_media_video.caption_entities == tuple(self.caption_entities) assert input_media_video.supports_streaming == self.supports_streaming assert isinstance(input_media_video.thumb, InputFile) + assert input_media_video.has_spoiler == self.has_spoiler def test_caption_entities_always_tuple(self): input_media_video = InputMediaVideo(self.media) @@ -166,6 +171,7 @@ def test_to_dict(self, input_media_video): ce.to_dict() for ce in input_media_video.caption_entities ] assert input_media_video_dict["supports_streaming"] == input_media_video.supports_streaming + assert input_media_video_dict["has_spoiler"] == input_media_video.has_spoiler def test_with_video(self, video): # noqa: F811 # fixture found in test_video @@ -198,6 +204,7 @@ class TestInputMediaPhoto: caption = "My Caption" parse_mode = "Markdown" caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] + has_spoiler = True def test_slot_behaviour(self, input_media_photo, mro_slots): inst = input_media_photo @@ -211,6 +218,7 @@ def test_expected_values(self, input_media_photo): assert input_media_photo.caption == self.caption assert input_media_photo.parse_mode == self.parse_mode assert input_media_photo.caption_entities == tuple(self.caption_entities) + assert input_media_photo.has_spoiler == self.has_spoiler def test_caption_entities_always_tuple(self): input_media_photo = InputMediaPhoto(self.media) @@ -225,6 +233,7 @@ def test_to_dict(self, input_media_photo): assert input_media_photo_dict["caption_entities"] == [ ce.to_dict() for ce in input_media_photo.caption_entities ] + assert input_media_photo_dict["has_spoiler"] == input_media_photo.has_spoiler def test_with_photo(self, photo): # noqa: F811 # fixture found in test_photo @@ -254,6 +263,7 @@ class TestInputMediaAnimation: width = 30 height = 30 duration = 1 + has_spoiler = True def test_slot_behaviour(self, input_media_animation, mro_slots): inst = input_media_animation @@ -268,6 +278,7 @@ def test_expected_values(self, input_media_animation): assert input_media_animation.parse_mode == self.parse_mode assert input_media_animation.caption_entities == tuple(self.caption_entities) assert isinstance(input_media_animation.thumb, InputFile) + assert input_media_animation.has_spoiler == self.has_spoiler def test_caption_entities_always_tuple(self): input_media_animation = InputMediaAnimation(self.media) @@ -285,6 +296,7 @@ def test_to_dict(self, input_media_animation): assert input_media_animation_dict["width"] == input_media_animation.width assert input_media_animation_dict["height"] == input_media_animation.height assert input_media_animation_dict["duration"] == input_media_animation.duration + assert input_media_animation_dict["has_spoiler"] == input_media_animation.has_spoiler def test_with_animation(self, animation): # noqa: F811 # fixture found in test_animation From 983d8fcae481e2fc88a557520d7485c63c2abbc4 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 30 Dec 2022 19:34:53 +0400 Subject: [PATCH 05/23] forgot versionadded for has_spoiler --- telegram/_files/inputmedia.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index 3f9f9a120c8..46bfa799164 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -147,6 +147,8 @@ class InputMediaAnimation(InputMedia): has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the animation needs to be covered with a spoiler animation. + .. versionadded:: 20.0 + Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`. media (:obj:`str` | :class:`telegram.InputFile`): Animation to send. @@ -166,6 +168,7 @@ class InputMediaAnimation(InputMedia): has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the animation is covered with a spoiler animation. + .. versionadded:: 20.0 """ __slots__ = ("duration", "height", "thumb", "width", "has_spoiler") @@ -237,6 +240,8 @@ class InputMediaPhoto(InputMedia): has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the photo needs to be covered with a spoiler animation. + .. versionadded:: 20.0 + Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`. media (:obj:`str` | :class:`telegram.InputFile`): Photo to send. @@ -251,6 +256,7 @@ class InputMediaPhoto(InputMedia): has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the photo is covered with a spoiler animation. + .. versionadded:: 20.0 """ __slots__ = ("has_spoiler",) @@ -327,6 +333,8 @@ class InputMediaVideo(InputMedia): has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered with a spoiler animation. + .. versionadded:: 20.0 + Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`. media (:obj:`str` | :class:`telegram.InputFile`): Video file to send. @@ -347,6 +355,7 @@ class InputMediaVideo(InputMedia): has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a spoiler animation. + .. versionadded:: 20.0 """ __slots__ = ("duration", "height", "thumb", "supports_streaming", "width", "has_spoiler") From 10d7bb7cf30c76a27978b6e5820185602fb272ac Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 30 Dec 2022 19:59:01 +0400 Subject: [PATCH 06/23] doc fixes in `_bot.py`: correct has_spoiler type, expand on edit_forum_topic args --- telegram/_bot.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 61f70441d67..c28f6910be4 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -963,7 +963,7 @@ async def send_photo( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the photo needs to be covered + has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the photo needs to be covered with a spoiler animation. .. versionadded:: 20.0 @@ -1418,7 +1418,7 @@ async def send_video( .. versionchanged:: 20.0 File paths as input is also accepted for bots *not* running in :paramref:`~telegram.Bot.local_mode`. - has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the video needs to be covered + has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the video needs to be covered with a spoiler animation. .. versionadded:: 20.0 @@ -1667,7 +1667,7 @@ async def send_animation( :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - has_spoiler (:obj:`int`, optional): Pass :obj:`True` if the animation needs to be + has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the animation needs to be covered with a spoiler animation. .. versionadded:: 20.0 @@ -6928,10 +6928,12 @@ async def edit_forum_topic( message_thread_id (:obj:`int`): |message_thread_id| name (:obj:`str`, optional): New topic name, :tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`- - :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. + :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. If + not specififed or empty, the current name of the topic will be kept. icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji shown as the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` - to get all allowed custom emoji identifiers. + to get all allowed custom emoji identifiers.Pass an empty string to remove the + icon. If not specified, the current icon will be kept. Returns: :obj:`bool`: On success, :obj:`True` is returned. From 01c349cf028f09ac788e01f2cc05694542883df2 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 30 Dec 2022 20:21:02 +0400 Subject: [PATCH 07/23] add filters for the new status updates --- telegram/ext/filters.py | 56 +++++++++++++++++++++++++++++++++++++++++ tests/test_filters.py | 20 +++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index cdd4cf62ccc..fae5d6834e3 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -1723,6 +1723,10 @@ def filter(self, update: Update) -> bool: or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update) or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update) or StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update) + or StatusUpdate.FORUM_TOPIC_EDITED.check_update(update) + or StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN.check_update(update) + or StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN.check_update(update) + or StatusUpdate.WRITE_ACCESS_ALLOWED.check_update(update) ) ALL = _All(name="filters.StatusUpdate.ALL") @@ -1761,6 +1765,46 @@ def filter(self, message: Message) -> bool: DELETE_CHAT_PHOTO = _DeleteChatPhoto(name="filters.StatusUpdate.DELETE_CHAT_PHOTO") """Messages that contain :attr:`telegram.Message.delete_chat_photo`.""" + class _ForumTopicEdited(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.forum_topic_edited) + + FORUM_TOPIC_EDITED = _ForumTopicEdited(name="filters.StatusUpdate.FORUM_TOPIC_EDITED") + """Messages that contain :attr:`telegram.Message.forum_topic_edited`. + + .. versionadded:: 20.0 + """ + + class _GeneralForumTopicHidden(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.general_forum_topic_hidden) + + GENERAL_FORUM_TOPIC_HIDDEN = _GeneralForumTopicHidden( + name="filters.StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN" + ) + """Messages that contain :attr:`telegram.Message.general_forum_topic_hidden`. + + .. versionadded:: 20.0 + """ + + class _GeneralForumTopicUnhidden(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.general_forum_topic_unhidden) + + GENERAL_FORUM_TOPIC_UNHIDDEN = _GeneralForumTopicUnhidden( + name="filters.StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN" + ) + """Messages that contain :attr:`telegram.Message.general_forum_topic_unhidden`. + + .. versionadded:: 20.0 + """ + class _LeftChatMember(MessageFilter): __slots__ = () @@ -1911,6 +1955,18 @@ def filter(self, message: Message) -> bool: .. versionadded:: 20.0 """ + class _WriteAccessAllowed(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.write_access_allowed) + + WRITE_ACCESS_ALLOWED = _WriteAccessAllowed(name="filters.StatusUpdate.WRITE_ACCESS_ALLOWED") + """Messages that contain :attr:`telegram.Message.write_access_allowed`. + + .. versionadded:: 20.0 + """ + class _ForumTopicCreated(MessageFilter): __slots__ = () diff --git a/tests/test_filters.py b/tests/test_filters.py index 41ed1cecd09..907f8d19fab 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1026,6 +1026,26 @@ def test_filters_status_update(self, update): assert filters.StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update) update.message.forum_topic_reopened = None + update.message.forum_topic_edited = "topic_edited" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.FORUM_TOPIC_EDITED.check_update(update) + update.message.forum_topic_edited = None + + update.message.general_forum_topic_hidden = "topic_hidden" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN.check_update(update) + update.message.general_forum_topic_hidden = None + + update.message.general_forum_topic_unhidden = "topic_unhidden" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN.check_update(update) + update.message.general_forum_topic_unhidden = None + + update.message.write_access_allowed = "allowed" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.WRITE_ACCESS_ALLOWED.check_update(update) + update.message.write_access_allowed = None + def test_filters_forwarded(self, update): assert not filters.FORWARDED.check_update(update) update.message.forward_date = datetime.datetime.utcnow() From af8c0853b770757368e492e0815ececaa9293dbf Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 30 Dec 2022 17:25:40 +0100 Subject: [PATCH 08/23] Fix: Hopefully all tests are happy now --- telegram/_chat.py | 2 ++ telegram/_message.py | 2 ++ telegram/_user.py | 2 ++ telegram/ext/_extbot.py | 2 +- tests/test_animation.py | 6 ++++-- tests/test_forum.py | 5 +++-- tests/test_user.py | 1 + 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/telegram/_chat.py b/telegram/_chat.py index 3d3f89016df..9402058a6e6 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -1356,6 +1356,7 @@ async def send_media_group( async def send_chat_action( self, action: str, + message_thread_id: int = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1376,6 +1377,7 @@ async def send_chat_action( return await self.get_bot().send_chat_action( chat_id=self.id, action=action, + message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/telegram/_message.py b/telegram/_message.py index f32773a57e1..2bac544a073 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -2044,6 +2044,7 @@ async def reply_dice( async def reply_chat_action( self, action: str, + message_thread_id: int = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2065,6 +2066,7 @@ async def reply_chat_action( """ return await self.get_bot().send_chat_action( chat_id=self.chat_id, + message_thread_id=message_thread_id, action=action, read_timeout=read_timeout, write_timeout=write_timeout, diff --git a/telegram/_user.py b/telegram/_user.py index 19fec324b8d..d93e35274df 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -577,6 +577,7 @@ async def send_audio( async def send_chat_action( self, action: str, + message_thread_id: int = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -597,6 +598,7 @@ async def send_chat_action( return await self.get_bot().send_chat_action( chat_id=self.id, action=action, + message_thread_id=message_thread_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index b6cc99931a5..736db9194b6 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -1291,7 +1291,7 @@ async def edit_forum_topic( async def edit_general_forum_topic( self, chat_id: Union[str, int], - name: str = None, + name: str, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, diff --git a/tests/test_animation.py b/tests/test_animation.py index 20fc34cc506..1051b618284 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -80,7 +80,8 @@ def test_expected_values(self, animation): assert animation.file_name.startswith("game.gif") == self.file_name.startswith("game.gif") assert isinstance(animation.thumb, PhotoSize) - @pytest.mark.flaky(3, 1) + @pytest.mark.dev + # @pytest.mark.flaky(3, 1) async def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file): message = await bot.send_animation( chat_id, @@ -107,7 +108,8 @@ async def test_send_all_args(self, bot, chat_id, animation_file, animation, thum assert message.animation.thumb.width == self.width assert message.animation.thumb.height == self.height assert message.has_protected_content - assert message.has_media_spoiler + # currently bugged + # assert message.has_media_spoiler @pytest.mark.flaky(3, 1) async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): diff --git a/tests/test_forum.py b/tests/test_forum.py index defd4f32d54..bf1d5dd2107 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import datetime + import pytest from telegram import ( @@ -258,13 +260,12 @@ async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_to async def test_edit_general_forum_topic(self, bot, forum_group_id): result = await bot.edit_general_forum_topic( chat_id=forum_group_id, - name="GENERAL_EDITED", + name=f"GENERAL_{datetime.datetime.now().timestamp()}", ) assert result is True, "Failed to edit general forum topic" # no way of checking the edited name, just the boolean result async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): - result = await bot.close_general_forum_topic( chat_id=forum_group_id, ) diff --git a/tests/test_user.py b/tests/test_user.py index 56c3c6eadf4..eba561409f9 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -254,6 +254,7 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "send_audio", make_assertion) assert await user.send_audio("test_audio") + @pytest.mark.dev async def test_instance_method_send_chat_action(self, monkeypatch, user): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == user.id and kwargs["action"] == "test_chat_action" From e9e5930f86ea2a7fe217d46b6389b67c87123c5c Mon Sep 17 00:00:00 2001 From: poolitzer Date: Fri, 30 Dec 2022 17:48:48 +0100 Subject: [PATCH 09/23] Fix: Xfail race condition --- tests/test_forum.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_forum.py b/tests/test_forum.py index bf1d5dd2107..afcab045fb3 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -265,13 +265,12 @@ async def test_edit_general_forum_topic(self, bot, forum_group_id): assert result is True, "Failed to edit general forum topic" # no way of checking the edited name, just the boolean result + @pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI") async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): result = await bot.close_general_forum_topic( chat_id=forum_group_id, ) assert result is True, "Failed to close general forum topic" - # bot will still be able to send a message to a closed topic, so can't test anything like - # the inability to post to the topic result = await bot.reopen_general_forum_topic( chat_id=forum_group_id, @@ -284,8 +283,6 @@ async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): chat_id=forum_group_id, ) assert result is True, "Failed to hide general forum topic" - # bot will still be able to send a message to a closed topic, so can't test anything like - # the inability to post to the topic result = await bot.unhide_general_forum_topic( chat_id=forum_group_id, From 3361ad13aa72cba77ac8432d6ff72bdec64ef41b Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 31 Dec 2022 00:58:01 +0400 Subject: [PATCH 10/23] numerous fixes: docs, removing dev marker, etc etc: Change a fixture scope to module, group WriteAccessAllowed test in a class --- telegram/_forumtopic.py | 2 +- telegram/_replykeyboardmarkup.py | 12 ++++++++---- tests/test_animation.py | 3 +-- tests/test_forum.py | 2 +- tests/test_user.py | 1 - tests/test_writeaccessallowed.py | 29 ++++++++++++++--------------- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/telegram/_forumtopic.py b/telegram/_forumtopic.py index 6e1d67b5be2..cfa493a3bc4 100644 --- a/telegram/_forumtopic.py +++ b/telegram/_forumtopic.py @@ -149,7 +149,7 @@ class ForumTopicEdited(TelegramObject): the topic icon, if it was edited; an empty string if the icon was removed. Attributes: - name (:obj:`str`):Optional. New name of the topic, if it was edited. + name (:obj:`str`): Optional. New name of the topic, if it was edited. icon_custom_emoji_id (:obj:`str`): Optional. New identifier of the custom emoji shown as the topic icon, if it was edited; an empty string if the icon was removed. """ diff --git a/telegram/_replykeyboardmarkup.py b/telegram/_replykeyboardmarkup.py index 7050c24d6ea..4d6dd4a92ac 100644 --- a/telegram/_replykeyboardmarkup.py +++ b/telegram/_replykeyboardmarkup.py @@ -86,7 +86,8 @@ class ReplyKeyboardMarkup(TelegramObject): .. versionadded:: 13.7 is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard when - the regular keyboard is hidden. + the regular keyboard is hidden. If :obj:`False`, the custom keyboard can be hidden and + opened with a keyboard icon. .. versionadded:: 20.0 @@ -179,7 +180,8 @@ def from_button( .. versionadded:: 13.7 is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard - when the regular keyboard is hidden. + when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the + custom keyboard can be hidden and opened with a keyboard icon. .. versionadded:: 20.0 """ @@ -236,7 +238,8 @@ def from_row( .. versionadded:: 13.7 is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard - when the regular keyboard is hidden. + when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the + custom keyboard can be hidden and opened with a keyboard icon. .. versionadded:: 20.0 @@ -294,7 +297,8 @@ def from_column( .. versionadded:: 13.7 is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard - when the regular keyboard is hidden. + when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the + custom keyboard can be hidden and opened with a keyboard icon. .. versionadded:: 20.0 diff --git a/tests/test_animation.py b/tests/test_animation.py index 1051b618284..e305b8d65af 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -80,8 +80,7 @@ def test_expected_values(self, animation): assert animation.file_name.startswith("game.gif") == self.file_name.startswith("game.gif") assert isinstance(animation.thumb, PhotoSize) - @pytest.mark.dev - # @pytest.mark.flaky(3, 1) + @pytest.mark.flaky(3, 1) async def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file): message = await bot.send_animation( chat_id, diff --git a/tests/test_forum.py b/tests/test_forum.py index afcab045fb3..e189c703492 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -380,7 +380,7 @@ def test_to_dict(self): assert action_dict == {} -@pytest.fixture +@pytest.fixture(scope="module") def topic_edited(emoji_id): return ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id=emoji_id) diff --git a/tests/test_user.py b/tests/test_user.py index eba561409f9..56c3c6eadf4 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -254,7 +254,6 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "send_audio", make_assertion) assert await user.send_audio("test_audio") - @pytest.mark.dev async def test_instance_method_send_chat_action(self, monkeypatch, user): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == user.id and kwargs["action"] == "test_chat_action" diff --git a/tests/test_writeaccessallowed.py b/tests/test_writeaccessallowed.py index 35cc2dcbbb4..f53079d8bbc 100644 --- a/tests/test_writeaccessallowed.py +++ b/tests/test_writeaccessallowed.py @@ -19,20 +19,19 @@ from telegram import WriteAccessAllowed -def test_slot_behaviour(mro_slots): - action = WriteAccessAllowed() - for attr in action.__slots__: - assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" - assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" +class TestWriteAccessAllowed: + def test_slot_behaviour(self, mro_slots): + action = WriteAccessAllowed() + for attr in action.__slots__: + assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" + def test_de_json(self): + action = WriteAccessAllowed.de_json({}, None) + assert action.api_kwargs == {} + assert isinstance(action, WriteAccessAllowed) -def test_de_json(): - action = WriteAccessAllowed.de_json({}, None) - assert action.api_kwargs == {} - assert isinstance(action, WriteAccessAllowed) - - -def test_to_dict(): - action = WriteAccessAllowed() - action_dict = action.to_dict() - assert action_dict == {} + def test_to_dict(self): + action = WriteAccessAllowed() + action_dict = action.to_dict() + assert action_dict == {} From cef6299cacbdc4cd2223f47270b3426f9b3c728f Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 31 Dec 2022 01:54:19 +0400 Subject: [PATCH 11/23] doc and typo fixes, and also add one more xfail to hide/unhide topic test --- telegram/_bot.py | 4 ++-- tests/test_forum.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index c28f6910be4..62c9c2c3944 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -3880,7 +3880,7 @@ async def get_chat_member( api_kwargs: JSONDict = None, ) -> ChatMember: """Use this method to get information about a member of a chat. The method is guaranteed - to work only if the bot is an administrator in the chat. + to work only if the bot is an administrator in the chat. .. seealso:: :meth:`telegram.Chat.get_member` @@ -6929,7 +6929,7 @@ async def edit_forum_topic( name (:obj:`str`, optional): New topic name, :tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`- :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. If - not specififed or empty, the current name of the topic will be kept. + not specified or empty, the current name of the topic will be kept. icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji shown as the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` to get all allowed custom emoji identifiers.Pass an empty string to remove the diff --git a/tests/test_forum.py b/tests/test_forum.py index e189c703492..d56674cef3a 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -277,6 +277,7 @@ async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): ) assert result is True, "Failed to reopen general forum topic" + @pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI") async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): result = await bot.hide_general_forum_topic( From 6ae626cad0be68da33a85e7a3a8c506f2e00b11d Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 31 Dec 2022 02:02:49 +0400 Subject: [PATCH 12/23] add filter for message.has_media_spoiler --- telegram/ext/filters.py | 15 +++++++++++++++ tests/test_filters.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index fae5d6834e3..e8e6840a2e4 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -58,6 +58,7 @@ "FORWARDED", "ForwardedFrom", "GAME", + "HAS_MEDIA_SPOILER", "HAS_PROTECTED_CONTENT", "INVOICE", "IS_AUTOMATIC_FORWARD", @@ -1384,6 +1385,20 @@ def filter(self, message: Message) -> bool: """Messages that contain :attr:`telegram.Message.game`.""" +class _HasMediaSpoiler(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.has_media_spoiler) + + +HAS_MEDIA_SPOILER = _HasMediaSpoiler(name="filters.HAS_MEDIA_SPOILER") +"""Messages that contain :attr:`telegram.Message.has_media_spoiler`. + + .. versionadded:: 20.0 +""" + + class _HasProtectedContent(MessageFilter): __slots__ = () diff --git a/tests/test_filters.py b/tests/test_filters.py index 907f8d19fab..dd02b8f62d9 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1835,6 +1835,11 @@ def test_filters_is_topic_message(self, update): update.message.is_topic_message = True assert filters.IS_TOPIC_MESSAGE.check_update(update) + def test_filters_has_media_spoiler(self, update): + assert not filters.HAS_MEDIA_SPOILER.check_update(update) + update.message.has_media_spoiler = True + assert filters.HAS_MEDIA_SPOILER.check_update(update) + def test_filters_has_protected_content(self, update): assert not filters.HAS_PROTECTED_CONTENT.check_update(update) update.message.has_protected_content = True From f523ba2708875a249cee62e0a5d5405577ec3598 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 31 Dec 2022 02:17:34 +0400 Subject: [PATCH 13/23] edit bot_methods.rst to include the new methods --- docs/source/inclusions/bot_methods.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index aff45cc164f..742cde020ed 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -267,16 +267,26 @@ * - :meth:`~telegram.Bot.close_forum_topic` - Used for closing a forum topic + * - :meth:`~telegram.Bot.close_general_forum_topic` + - Used for closing the general forum topic * - :meth:`~telegram.Bot.create_forum_topic` - Used to create a topic * - :meth:`~telegram.Bot.delete_forum_topic` - Used for deleting a forum topic * - :meth:`~telegram.Bot.edit_forum_topic` - Used to edit a topic - * - :meth:`~telegram.Bot.reopen_forum_topic` - - Used to reopen a topic + * - :meth:`~telegram.Bot.edit_general_forum_topic` + - Used to edit the general topic * - :meth:`~telegram.Bot.get_forum_topic_icon_stickers` - Used to get custom emojis to use as topic icons + * - :meth:`~telegram.Bot.hide_general_forum_topic` + - Used to hide the general topic + * - :meth:`~telegram.Bot.unhide_general_forum_topic` + - Used to unhide the general topic + * - :meth:`~telegram.Bot.reopen_forum_topic` + - Used to reopen a topic + * - :meth:`~telegram.Bot.reopen_general_forum_topic` + - Used to reopen the general topic * - :meth:`~telegram.Bot.unpin_all_forum_topic_messages` - Used to unpin all messages in a forum topic From bc481c1bc73a8e9d4f1ecb60e814fa163bc12a6a Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 31 Dec 2022 02:19:47 +0400 Subject: [PATCH 14/23] update bot api version number --- README.rst | 2 +- README_RAW.rst | 2 +- telegram/constants.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5229da62f08..93c3afeeaf8 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-6.3-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-6.4-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions diff --git a/README_RAW.rst b/README_RAW.rst index 47dd8c7fe8d..4a981786148 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -14,7 +14,7 @@ :target: https://pypi.org/project/python-telegram-bot-raw/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-6.3-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-6.4-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions diff --git a/telegram/constants.py b/telegram/constants.py index 97a7d063e1d..6936f9003b4 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -111,7 +111,7 @@ def __str__(self) -> str: #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=3) +BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=4) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. From c62f11380b9ab5a9133d6b919120259093144583 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 30 Dec 2022 20:30:20 +0400 Subject: [PATCH 15/23] rearrange ForumTopic* classes in filters alphabetically --- telegram/ext/filters.py | 72 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index e8e6840a2e4..89ad50ed0ee 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -1780,6 +1780,30 @@ def filter(self, message: Message) -> bool: DELETE_CHAT_PHOTO = _DeleteChatPhoto(name="filters.StatusUpdate.DELETE_CHAT_PHOTO") """Messages that contain :attr:`telegram.Message.delete_chat_photo`.""" + class _ForumTopicClosed(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.forum_topic_closed) + + FORUM_TOPIC_CLOSED = _ForumTopicClosed(name="filters.StatusUpdate.FORUM_TOPIC_CLOSED") + """Messages that contain :attr:`telegram.Message.forum_topic_closed`. + + .. versionadded:: 20.0 + """ + + class _ForumTopicCreated(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.forum_topic_created) + + FORUM_TOPIC_CREATED = _ForumTopicCreated(name="filters.StatusUpdate.FORUM_TOPIC_CREATED") + """Messages that contain :attr:`telegram.Message.forum_topic_created`. + + .. versionadded:: 20.0 + """ + class _ForumTopicEdited(MessageFilter): __slots__ = () @@ -1792,6 +1816,18 @@ def filter(self, message: Message) -> bool: .. versionadded:: 20.0 """ + class _ForumTopicReopened(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.forum_topic_reopened) + + FORUM_TOPIC_REOPENED = _ForumTopicReopened(name="filters.StatusUpdate.FORUM_TOPIC_REOPENED") + """Messages that contain :attr:`telegram.Message.forum_topic_reopened`. + + .. versionadded:: 20.0 + """ + class _GeneralForumTopicHidden(MessageFilter): __slots__ = () @@ -1982,42 +2018,6 @@ def filter(self, message: Message) -> bool: .. versionadded:: 20.0 """ - class _ForumTopicCreated(MessageFilter): - __slots__ = () - - def filter(self, message: Message) -> bool: - return bool(message.forum_topic_created) - - FORUM_TOPIC_CREATED = _ForumTopicCreated(name="filters.StatusUpdate.FORUM_TOPIC_CREATED") - """Messages that contain :attr:`telegram.Message.forum_topic_created`. - - .. versionadded:: 20.0 - """ - - class _ForumTopicClosed(MessageFilter): - __slots__ = () - - def filter(self, message: Message) -> bool: - return bool(message.forum_topic_closed) - - FORUM_TOPIC_CLOSED = _ForumTopicClosed(name="filters.StatusUpdate.FORUM_TOPIC_CLOSED") - """Messages that contain :attr:`telegram.Message.forum_topic_closed`. - - .. versionadded:: 20.0 - """ - - class _ForumTopicReopened(MessageFilter): - __slots__ = () - - def filter(self, message: Message) -> bool: - return bool(message.forum_topic_reopened) - - FORUM_TOPIC_REOPENED = _ForumTopicReopened(name="filters.StatusUpdate.FORUM_TOPIC_REOPENED") - """Messages that contain :attr:`telegram.Message.forum_topic_reopened`. - - .. versionadded:: 20.0 - """ - class Sticker: """Filters messages which contain a sticker. From 82e6aa25ac97feaa1681e4415423a63360a724e9 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 04:18:27 +0400 Subject: [PATCH 16/23] review: Remove unnecessary message shortcuts --- telegram/_message.py | 167 ---------------------------------------- tests/test_animation.py | 6 +- tests/test_message.py | 105 ------------------------- 3 files changed, 4 insertions(+), 274 deletions(-) diff --git a/telegram/_message.py b/telegram/_message.py index 2bac544a073..d33944f495b 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -3053,173 +3053,6 @@ async def unpin_all_forum_topic_messages( api_kwargs=api_kwargs, ) - async def edit_general_forum_topic( - self, - name: str, - *, - 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, - ) -> bool: - """Shortcut for:: - - await bot.edit_general_forum_topic( - chat_id=message.chat_id, *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_general_forum_topic`. - - .. versionadded:: 20.0 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return await self.get_bot().edit_general_forum_topic( - chat_id=self.chat_id, - name=name, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=api_kwargs, - ) - - async def close_general_forum_topic( - self, - *, - 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, - ) -> bool: - """Shortcut for:: - - await bot.close_general_forum_topic( - chat_id=message.chat_id, *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.close_general_forum_topic`. - - .. versionadded:: 20.0 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return await self.get_bot().close_general_forum_topic( - chat_id=self.chat_id, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=api_kwargs, - ) - - async def reopen_general_forum_topic( - self, - *, - 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, - ) -> bool: - """Shortcut for:: - - await bot.reopen_general_forum_topic( - chat_id=message.chat_id, *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.reopen_general_forum_topic`. - - .. versionadded:: 20.0 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return await self.get_bot().reopen_general_forum_topic( - chat_id=self.chat_id, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=api_kwargs, - ) - - async def hide_general_forum_topic( - self, - *, - 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, - ) -> bool: - """Shortcut for:: - - await bot.hide_general_forum_topic( - chat_id=message.chat_id, *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.hide_general_forum_topic`. - - .. versionadded:: 20.0 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return await self.get_bot().hide_general_forum_topic( - chat_id=self.chat_id, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=api_kwargs, - ) - - async def unhide_general_forum_topic( - self, - *, - 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, - ) -> bool: - """Shortcut for:: - - await bot.unhide_general_forum_topic( - chat_id=message.chat_id, *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unhide_general_forum_topic`. - - .. versionadded:: 20.0 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return await self.get_bot().unhide_general_forum_topic( - chat_id=self.chat_id, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=api_kwargs, - ) - def parse_entity(self, entity: MessageEntity) -> str: """Returns the text from a given :class:`telegram.MessageEntity`. diff --git a/tests/test_animation.py b/tests/test_animation.py index e305b8d65af..fe64033e040 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -107,8 +107,10 @@ async def test_send_all_args(self, bot, chat_id, animation_file, animation, thum assert message.animation.thumb.width == self.width assert message.animation.thumb.height == self.height assert message.has_protected_content - # currently bugged - # assert message.has_media_spoiler + try: + assert message.has_media_spoiler + except AssertionError: + pytest.xfail("This is a bug on Telegram's end") @pytest.mark.flaky(3, 1) async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): diff --git a/tests/test_message.py b/tests/test_message.py index f2f84465adc..7891d1e43ea 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -1821,111 +1821,6 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(message.get_bot(), "unpin_all_forum_topic_messages", make_assertion) assert await message.unpin_all_forum_topic_messages() - async def test_edit_general_forum_topic(self, monkeypatch, message): - async def make_assertion(*_, **kwargs): - return kwargs["chat_id"] == message.chat_id and kwargs["name"] == "WhatAName" - - assert check_shortcut_signature( - Message.edit_general_forum_topic, - Bot.edit_general_forum_topic, - ["chat_id"], - [], - ) - assert await check_shortcut_call( - message.edit_general_forum_topic, - message.get_bot(), - "edit_general_forum_topic", - shortcut_kwargs=["chat_id"], - ) - assert await check_defaults_handling(message.edit_general_forum_topic, message.get_bot()) - - monkeypatch.setattr(message.get_bot(), "edit_general_forum_topic", make_assertion) - assert await message.edit_general_forum_topic(name="WhatAName") - - async def test_close_general_forum_topic(self, monkeypatch, message): - async def make_assertion(*_, **kwargs): - return kwargs["chat_id"] == message.chat_id - - assert check_shortcut_signature( - Message.close_general_forum_topic, - Bot.close_general_forum_topic, - ["chat_id"], - [], - ) - assert await check_shortcut_call( - message.close_general_forum_topic, - message.get_bot(), - "close_general_forum_topic", - shortcut_kwargs=["chat_id"], - ) - assert await check_defaults_handling(message.close_general_forum_topic, message.get_bot()) - - monkeypatch.setattr(message.get_bot(), "close_general_forum_topic", make_assertion) - assert await message.close_general_forum_topic() - - async def test_reopen_general_forum_topic(self, monkeypatch, message): - async def make_assertion(*_, **kwargs): - return kwargs["chat_id"] == message.chat_id - - assert check_shortcut_signature( - Message.reopen_general_forum_topic, - Bot.reopen_general_forum_topic, - ["chat_id"], - [], - ) - assert await check_shortcut_call( - message.reopen_general_forum_topic, - message.get_bot(), - "reopen_general_forum_topic", - shortcut_kwargs=["chat_id"], - ) - assert await check_defaults_handling(message.reopen_general_forum_topic, message.get_bot()) - - monkeypatch.setattr(message.get_bot(), "reopen_general_forum_topic", make_assertion) - assert await message.reopen_general_forum_topic() - - async def test_hide_general_forum_topic(self, monkeypatch, message): - async def make_assertion(*_, **kwargs): - return kwargs["chat_id"] == message.chat_id - - assert check_shortcut_signature( - Message.hide_general_forum_topic, - Bot.hide_general_forum_topic, - ["chat_id"], - [], - ) - assert await check_shortcut_call( - message.hide_general_forum_topic, - message.get_bot(), - "hide_general_forum_topic", - shortcut_kwargs=["chat_id"], - ) - assert await check_defaults_handling(message.hide_general_forum_topic, message.get_bot()) - - monkeypatch.setattr(message.get_bot(), "hide_general_forum_topic", make_assertion) - assert await message.hide_general_forum_topic() - - async def test_unhide_general_forum_topic(self, monkeypatch, message): - async def make_assertion(*_, **kwargs): - return kwargs["chat_id"] == message.chat_id - - assert check_shortcut_signature( - Message.unhide_general_forum_topic, - Bot.unhide_general_forum_topic, - ["chat_id"], - [], - ) - assert await check_shortcut_call( - message.unhide_general_forum_topic, - message.get_bot(), - "unhide_general_forum_topic", - shortcut_kwargs=["chat_id"], - ) - assert await check_defaults_handling(message.unhide_general_forum_topic, message.get_bot()) - - monkeypatch.setattr(message.get_bot(), "unhide_general_forum_topic", make_assertion) - assert await message.unhide_general_forum_topic() - def test_equality(self): id_ = 1 a = Message( From 4a33700bcd291554030e0aaeb1dfc867ec019b51 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 04:20:27 +0400 Subject: [PATCH 17/23] Review: Use try-except and change xfail msg --- tests/test_forum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_forum.py b/tests/test_forum.py index d56674cef3a..0a22f8f7773 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -265,7 +265,7 @@ async def test_edit_general_forum_topic(self, bot, forum_group_id): assert result is True, "Failed to edit general forum topic" # no way of checking the edited name, just the boolean result - @pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI") + @pytest.mark.xfail(reason="Can fail b/c all CI bots use the same general topic") async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): result = await bot.close_general_forum_topic( chat_id=forum_group_id, @@ -277,7 +277,7 @@ async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): ) assert result is True, "Failed to reopen general forum topic" - @pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI") + @pytest.mark.xfail(reason="Can fail b/c all CI bots use the same general topic") async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): result = await bot.hide_general_forum_topic( From d7a8c0968ab4dd0e044a1dd00396dbb5d62f16c6 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 04:58:17 +0400 Subject: [PATCH 18/23] add inputmedia spoiler test --- tests/test_inputmedia.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index b88e1075c2c..6db2d656ea8 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -621,6 +621,22 @@ async def test_send_media_group_all_args(self, bot, raw_bot, chat_id, media_grou ) assert all(mes.has_protected_content for mes in messages) + @pytest.mark.flaky(3, 1) + async def test_send_media_group_with_spoiler( + self, bot, chat_id, photo_file, video_file + ): # noqa: F811 + # Media groups can't contain Animations, so that is tested in test_animation.py + media = [ + InputMediaPhoto(photo_file, has_spoiler=True), + InputMediaVideo(video_file, has_spoiler=True), + ] + messages = await bot.send_media_group(chat_id, media) + assert isinstance(messages, tuple) + assert len(messages) == 2 + assert all(isinstance(mes, Message) for mes in messages) + assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) + assert all(mes.has_media_spoiler for mes in messages) + @pytest.mark.flaky(3, 1) async def test_send_media_group_custom_filename( self, From 13bfced7cfe3d30c5fe5e585560a8f41df3d9c74 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 04:59:06 +0400 Subject: [PATCH 19/23] update version number in readme properly --- README.rst | 2 +- README_RAW.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 93c3afeeaf8..81f95162639 100644 --- a/README.rst +++ b/README.rst @@ -93,7 +93,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 **6.2** are supported. +All types and methods of the Telegram Bot API **6.4** are supported. Installing ========== diff --git a/README_RAW.rst b/README_RAW.rst index 4a981786148..d2bb32fdaee 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -89,7 +89,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 **6.2** are supported. +All types and methods of the Telegram Bot API **6.4** are supported. Installing ========== From 7777341afcddcdf3d42e05a54804021944298077 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 05:14:39 +0400 Subject: [PATCH 20/23] fix docs build and pre-commit --- telegram/_bot.py | 25 ++++++++++--------------- tests/test_inputmedia.py | 4 ++-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 62c9c2c3944..4884d6c706f 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -7164,10 +7164,9 @@ async def edit_general_forum_topic( """ Use this method to edit the name of the 'General' topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have - :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + :attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. - .. seealso:: :meth:`telegram.Message.edit_general_forum_topic`, - :meth:`telegram.Chat.edit_general_forum_topic`, + .. seealso:: :meth:`telegram.Chat.edit_general_forum_topic` .. versionadded:: 20.0 @@ -7210,10 +7209,9 @@ async def close_general_forum_topic( """ Use this method to close an open 'General' topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have - :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + :attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. - .. seealso:: :meth:`telegram.Message.close_general_forum_topic`, - :meth:`telegram.Chat.close_general_forum_topic`, + .. seealso:: :meth:`telegram.Chat.close_general_forum_topic` .. versionadded:: 20.0 @@ -7253,11 +7251,10 @@ async def reopen_general_forum_topic( """ Use this method to reopen a closed 'General' topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have - :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + :attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. The topic will be automatically unhidden if it was hidden. - .. seealso:: :meth:`telegram.Message.reopen_general_forum_topic`, - :meth:`telegram.Chat.reopen_general_forum_topic`, + .. seealso:: :meth:`telegram.Chat.reopen_general_forum_topic` .. versionadded:: 20.0 @@ -7297,11 +7294,10 @@ async def hide_general_forum_topic( """ Use this method to hide the 'General' topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have - :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + :attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. The topic will be automatically closed if it was open. - .. seealso:: :meth:`telegram.Message.hide_general_forum_topic`, - :meth:`telegram.Chat.hide_general_forum_topic`, + .. seealso:: :meth:`telegram.Chat.hide_general_forum_topic` .. versionadded:: 20.0 @@ -7341,10 +7337,9 @@ async def unhide_general_forum_topic( """ Use this method to unhide the 'General' topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have - :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. + :attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights. - .. seealso:: :meth:`telegram.Message.unhide_general_forum_topic`, - :meth:`telegram.Chat.unhide_general_forum_topic`, + .. seealso:: :meth:`telegram.Chat.unhide_general_forum_topic` .. versionadded:: 20.0 diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index 6db2d656ea8..6b33c36340e 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -623,8 +623,8 @@ async def test_send_media_group_all_args(self, bot, raw_bot, chat_id, media_grou @pytest.mark.flaky(3, 1) async def test_send_media_group_with_spoiler( - self, bot, chat_id, photo_file, video_file - ): # noqa: F811 + self, bot, chat_id, photo_file, video_file # noqa: F811 + ): # Media groups can't contain Animations, so that is tested in test_animation.py media = [ InputMediaPhoto(photo_file, has_spoiler=True), From 190c20d0e597ee709cb1db97ce87d50f32c48816 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 1 Jan 2023 05:34:50 +0400 Subject: [PATCH 21/23] override __init__ to freeze the status update classes --- telegram/_forumtopic.py | 10 ++++++++++ telegram/_writeaccessallowed.py | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/telegram/_forumtopic.py b/telegram/_forumtopic.py index cfa493a3bc4..e7b007fb3d8 100644 --- a/telegram/_forumtopic.py +++ b/telegram/_forumtopic.py @@ -182,6 +182,11 @@ class GeneralForumTopicHidden(TelegramObject): __slots__ = () + def __init__(self, *, api_kwargs: JSONDict = None): + super().__init__(api_kwargs=api_kwargs) + + self._freeze() + class GeneralForumTopicUnhidden(TelegramObject): """ @@ -192,3 +197,8 @@ class GeneralForumTopicUnhidden(TelegramObject): """ __slots__ = () + + def __init__(self, *, api_kwargs: JSONDict = None): + super().__init__(api_kwargs=api_kwargs) + + self._freeze() diff --git a/telegram/_writeaccessallowed.py b/telegram/_writeaccessallowed.py index 5c951c9ecb3..389a2d59eb4 100644 --- a/telegram/_writeaccessallowed.py +++ b/telegram/_writeaccessallowed.py @@ -18,6 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains objects related to the write access allowed service message.""" from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict class WriteAccessAllowed(TelegramObject): @@ -29,3 +30,8 @@ class WriteAccessAllowed(TelegramObject): """ __slots__ = () + + def __init__(self, *, api_kwargs: JSONDict = None): + super().__init__(api_kwargs=api_kwargs) + + self._freeze() From 3b551d3853aea10da80d0f9c1e4db9acb82df67d Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:40:10 +0100 Subject: [PATCH 22/23] Give each test bot its own forum group --- .github/workflows/test.yml | 2 +- tests/bots.py | 18 +++++++++--------- tests/test_forum.py | 3 --- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1809bc7acb..c836e2e62ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,7 +83,7 @@ jobs: exit ${status} env: JOB_INDEX: ${{ strategy.job-index }} - BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzkwOTgzOTk3IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ0NjAyMjUyMiIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDE0OTY5MTc3NTAiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzMzODcxNDYxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ3ODI5MzcxNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzNjM5MzI1NzMiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDA3ODM2NjA1IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjc5NjAwMDI2IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEyOTMwNzkxNjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDExODU1MDk2MzYiLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDg0Nzk3NjEyIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDAyMjU1MDcwIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9LCB7InRva2VuIjogIjU1MTg2NDU0MTE6QUFHdzBxaEs3ZTRHbmoxWjJjc1BBQzdaYWtvTWs1NkVKZmsiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNRE0wT1RCbE9UUXpNVEU1IiwgIm5hbWUiOiAiUFRCIFRlc3QgQm90IFszXSIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTgwMzgxMDE5NiIsICJ1c2VybmFtZSI6ICJwdGJfdGVzdF8wM19ib3QifSwgeyJ0b2tlbiI6ICI1NzM3MDE4MzU2OkFBSDEzOFN1aUtRRjBMRENXc2ZnV2VYZmpKNWQ2M2tDV0xBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TjJWaVpqUmxaak01TlRNdyIsICJuYW1lIjogIlBUQiBUZXN0IEJvdCBbNF0iLCAidXNlcm5hbWUiOiAicHRiX3Rlc3RfMDRfYm90IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxODQyNDM5NjQxIn0sIHsidG9rZW4iOiAiNTc0NDY0NDUyMjpBQUVBZHNyRjBoQzZwNkhVTzBQMDFROGJfakNoVTUyWEctTSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpqSmtZVGd5TmpnMlpHRTAiLCAibmFtZSI6ICJQVEIgVGVzdCBCb3QgWzVdIiwgInVzZXJuYW1lIjogInB0Yl90ZXN0XzA1X2JvdCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTg1NTM2MDk4NiJ9XQ== + BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJuYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzOTA5ODM5OTciLCAidXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8yN19ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE3MTA4NTA4MjIifSwgeyJ0b2tlbiI6ICI2NzE0Njg4ODY6QUFHUEdmY2lSSUJVTkZlODI0dUlWZHE3SmUzX1luQVROR3ciLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpaR1l3T1Rsa016TXhOMlkyIiwgIm5hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ0NjAyMjUyMiIsICJ1c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM0X2JvdCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTg5MTQ0MTc5MSJ9LCB7InRva2VuIjogIjYyOTMyNjUzODpBQUZSclpKckI3b0IzbXV6R3NHSlhVdkdFNUNRek01Q1U0byIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk1tTTVZV0poWXpreE0yVTEiLCAibmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy41IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDk2OTE3NzUwIiwgInVzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90IiwgImZvcnVtX2dyb3VwX2lkIjogIi0xMDAxNTc3NTA0Nzg3In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJuYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjYiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzMzM4NzE0NjEiLCAidXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNl9ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE4Njc5MDExNzIifSwgeyJ0b2tlbiI6ICI2OTUxMDQwODg6QUFIZnp5bElPalNJSVMtZU9uSTIweTJFMjBIb2RIc2Z6LTAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpPR1ExTURnd1pqSXdaakZsIiwgIm5hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ3ODI5MzcxNCIsICJ1c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM3X2JvdCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTg2NDA1NDg3OSJ9LCB7InRva2VuIjogIjY5MTQyMzU1NDpBQUY4V2tqQ1pibkhxUF9pNkdoVFlpckZFbFpyR2FZT2hYMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllqYzVOVGhpTW1ReU1XVmgiLCAibmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzYzOTMyNTczIiwgInVzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90IiwgImZvcnVtX2dyb3VwX2lkIjogIi0xMDAxODY3ODU1OTM2In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJuYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAzLjUiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDE0MDc4MzY2MDUiLCAidXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfcHlweV8zNV9ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE1NTg5OTAyODIifSwgeyJ0b2tlbiI6ICI2OTAwOTEzNDc6QUFGTG1SNXBBQjVZY3BlX21PaDd6TTRKRkJPaDB6M1QwVG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpaRGhsTnpFNU1Ea3dZV0ppIiwgIm5hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjc5NjAwMDI2IiwgInVzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8zNF9ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE3MjU2OTEzODcifSwgeyJ0b2tlbiI6ICI2OTQzMDgwNTI6QUFFQjJfc29uQ2s1NUxZOUJHOUFPLUg4anhpUFM1NW9vQkEiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZbVppWVdabU1qSmhaR015IiwgIm5hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjkzMDc5MTY1IiwgInVzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE1NjU4NTU5ODcifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDExODU1MDk2MzYiLCAidXNlcm5hbWUiOiAicHRiXzBfYm90IiwgImZvcnVtX2dyb3VwX2lkIjogIi0xMDAxODE5MDM3MzExIn0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDg0Nzk3NjEyIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTc5NzMwODQ0NCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDAyMjU1MDcwIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTUyMzU3NTA3MiJ9LCB7InRva2VuIjogIjU1MTg2NDU0MTE6QUFHdzBxaEs3ZTRHbmoxWjJjc1BBQzdaYWtvTWs1NkVKZmsiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNRE0wT1RCbE9UUXpNVEU1IiwgIm5hbWUiOiAiUFRCIFRlc3QgQm90IFszXSIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTgwMzgxMDE5NiIsICJ1c2VybmFtZSI6ICJwdGJfdGVzdF8wM19ib3QiLCAiZm9ydW1fZ3JvdXBfaWQiOiAiLTEwMDE2MTk2NzMyNjEifSwgeyJ0b2tlbiI6ICI1NzM3MDE4MzU2OkFBSDEzOFN1aUtRRjBMRENXc2ZnV2VYZmpKNWQ2M2tDV0xBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TjJWaVpqUmxaak01TlRNdyIsICJuYW1lIjogIlBUQiBUZXN0IEJvdCBbNF0iLCAidXNlcm5hbWUiOiAicHRiX3Rlc3RfMDRfYm90IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxODQyNDM5NjQxIiwgImZvcnVtX2dyb3VwX2lkIjogIi0xMDAxODQyOTk2MTk5In0sIHsidG9rZW4iOiAiNTc0NDY0NDUyMjpBQUVBZHNyRjBoQzZwNkhVTzBQMDFROGJfakNoVTUyWEctTSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpqSmtZVGd5TmpnMlpHRTAiLCAibmFtZSI6ICJQVEIgVGVzdCBCb3QgWzVdIiwgInVzZXJuYW1lIjogInB0Yl90ZXN0XzA1X2JvdCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTg1NTM2MDk4NiIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTY0NDM2NjkwMiJ9XQ== TEST_WITH_OPT_DEPS : "false" TEST_BUILD: "true" shell: bash --noprofile --norc {0} diff --git a/tests/bots.py b/tests/bots.py index 4ed5160a5ac..97cdd0ed665 100644 --- a/tests/bots.py +++ b/tests/bots.py @@ -27,15 +27,15 @@ # purposes than testing. FALLBACKS = ( "W3sidG9rZW4iOiAiNTc5Njk0NzE0OkFBRnBLOHc2emtrVXJENHhTZVl3RjNNTzhlLTRHcm1jeTdjIiwgInBheW1lbnRfc" - "HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2lkIjogIjY3NTY2Nj" - "IyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE" - "1OTQwNCIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0" - "cyBmYWxsYmFjayAxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzFfYm90In0sIHsidG9rZW4iOiAiNTU4M" - "Tk0MDY2OkFBRndEUElGbHpHVWxDYVdIdFRPRVg0UkZyWDh1OURNcWZvIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOi" - "AiMjg0Njg1MDYzOlRFU1Q6WWpFd09EUXdNVEZtTkRjeSIsICJjaGF0X2lkIjogIjY3NTY2NjIyNCIsICJzdXBlcl9ncm9" - "1cF9pZCI6ICItMTAwMTIyMTIxNjgzMCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE1OTQwNCIsICJjaGFubmVs" - "X2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgI" - "mJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzJfYm90In1d " + "HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2 lkIjogIjY3NTY2N" + "jIyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTgzOD" + "AwNDU3NyIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIi wgIm5hbWUiOiAiUFRCIHRlc3RzIG" + "ZhbGxiYWNrIDEiLCAidXNlcm5hbWUiOiAiQHB0Yl9mYWxsYmFja18xX2JvdCJ9LCB7InRva2VuIjogIjU1ODE5NDA2Njp" + "BQUZ3RFBJRmx6R1VsQ2FXSHRUT0VYNFJGclg4dTlETXFmbyIsIC JwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY" + "4NTA2MzpURVNUOllqRXdPRFF3TVRGbU5EY3kiLCAiY2hhdF9pZCI6ICI2NzU2NjYyMjQiLCAic3VwZXJfZ3JvdXBfaWQi" + "OiAiLTEwMDEyMjEyMTY4MzAiLCAiZm9ydW1fZ3 JvdXBfaWQiOiAiLTEwMDE4NTc4NDgzMTQiLCAiY2hhbm5lbF9pZCI6" + "ICJAcHl0aG9udGVsZWdyYW1ib3R0ZXN0cyIsICJuYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgInVzZXJuYW1lI" + "jogIkBwdGJfZmFsbGJhY2tfMl9ib3QifV0=" ) GITHUB_ACTION = os.getenv("GITHUB_ACTION", None) diff --git a/tests/test_forum.py b/tests/test_forum.py index 0a22f8f7773..867255effb8 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -236,7 +236,6 @@ async def test_close_and_reopen_forum_topic(self, bot, forum_group_id, real_topi ) assert result is True, "Failed to reopen forum topic" - @pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI") async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic): message_thread_id = real_topic.message_thread_id @@ -265,7 +264,6 @@ async def test_edit_general_forum_topic(self, bot, forum_group_id): assert result is True, "Failed to edit general forum topic" # no way of checking the edited name, just the boolean result - @pytest.mark.xfail(reason="Can fail b/c all CI bots use the same general topic") async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): result = await bot.close_general_forum_topic( chat_id=forum_group_id, @@ -277,7 +275,6 @@ async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id): ) assert result is True, "Failed to reopen general forum topic" - @pytest.mark.xfail(reason="Can fail b/c all CI bots use the same general topic") async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): result = await bot.hide_general_forum_topic( From 7e585dc5605c56efb2655f03450d84d4843a97e8 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 1 Jan 2023 16:20:56 +0100 Subject: [PATCH 23/23] Try fixing forum tests --- tests/test_forum.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_forum.py b/tests/test_forum.py index 867255effb8..c37e1a3d5ca 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -151,8 +151,8 @@ def test_equality(self, emoji_id, forum_group_id): assert hash(a) != hash(e) +@pytest.mark.flaky(3, 1) class TestForumMethods: - @pytest.mark.flaky(3, 1) async def test_create_forum_topic(self, real_topic): result = real_topic assert isinstance(result, ForumTopic) @@ -174,7 +174,6 @@ async def test_create_forum_topic_with_only_required_args(self, bot, forum_group ) assert result is True, "Failed to delete forum topic" - @pytest.mark.flaky(3, 1) async def test_get_forum_topic_icon_stickers(self, bot): emoji_sticker_list = await bot.get_forum_topic_icon_stickers() first_sticker = emoji_sticker_list[0] @@ -207,7 +206,6 @@ async def test_edit_forum_topic(self, emoji_id, forum_group_id, bot, real_topic) assert result is True, "Failed to edit forum topic" # no way of checking the edited name, just the boolean result - @pytest.mark.flaky(3, 1) async def test_send_message_to_topic(self, bot, forum_group_id, real_topic): message_thread_id = real_topic.message_thread_id @@ -287,6 +285,12 @@ async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id): ) assert result is True, "Failed to unhide general forum topic" + # hiding the general topic also closes it, so we reopen it + result = await bot.reopen_general_forum_topic( + chat_id=forum_group_id, + ) + assert result is True, "Failed to reopen general forum topic" + @pytest.fixture def topic_created():