diff --git a/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml b/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml index 560895c4486..7bb836c47c6 100644 --- a/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml +++ b/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml @@ -1,5 +1,6 @@ -features = "Full Support for Bot API 10.0" -[[pull_requests]] -uid = "5229" -author_uids = ["aelkheir"] -closes_threads = [] +highlights = "Full Support for Bot API 10.0" + +pull_requests = [ + { uid = "5229", author_uid = "aelkheir", closes_threads = ["5228"] }, + { uid = "5230", author_uid = "harshil21" }, +] diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index 72f58dea7bd..8ee2734a81f 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -104,6 +104,10 @@ - Used for stopping the running poll * - :meth:`~telegram.Bot.set_message_reaction` - Used for setting reactions on messages + * - :meth:`~telegram.Bot.delete_message_reaction` + - Used for deleting reactions on messages + * - :meth:`~telegram.Bot.delete_all_message_reactions` + - Used for deleting all reactions by a chat or user .. raw:: html diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index d9d1f83b069..3491a75cffa 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -5125,6 +5125,7 @@ async def get_chat( async def get_chat_administrators( self, chat_id: str | int, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -5140,18 +5141,21 @@ async def get_chat_administrators( Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| + return_bots (:obj:`bool`, optional): Pass :obj:`True` to additionally receive all bots + that are administrators of the chat. By default, bots other than the current bot + are omitted. + + .. versionadded:: NEXT.VERSION Returns: tuple[:class:`telegram.ChatMember`]: On success, returns a tuple of ``ChatMember`` - objects that contains information about all chat administrators except - other bots. If the chat is a group or a supergroup and no administrators were - appointed, only the creator will be returned. + objects that contains information about all chat administrators. Raises: :class:`telegram.error.TelegramError` """ - data: JSONDict = {"chat_id": chat_id} + data: JSONDict = {"chat_id": chat_id, "return_bots": return_bots} result = await self._post( "getChatAdministrators", data, @@ -12283,6 +12287,106 @@ async def save_prepared_keyboard_button( self, ) + async def delete_message_reaction( + self, + chat_id: int | str, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Use this method to remove a reaction from a message in a group or a supergroup chat. + The bot must have the :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` + administrator right in the chat. + + .. versionadded:: NEXT.VERSION + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + message_id (:obj:`int`): Identifier of the target message. + user_id (:obj:`int`, optional): Identifier of the user whose reaction will be removed, + if the reaction were added by a user. + actor_chat_id (:obj:`int`, optional): Identifier of the chat whose reaction will be + removed, if the reaction were added by a chat. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "chat_id": chat_id, + "message_id": message_id, + "user_id": user_id, + "actor_chat_id": actor_chat_id, + } + + return await self._post( + "deleteMessageReaction", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_message_reactions( + self, + chat_id: int | str, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Use this method to remove up to ``10000`` recent reactions in a group or a supergroup chat + added by a given user or chat. The bot must have the + :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` administrator right in the + chat. + + .. versionadded:: NEXT.VERSION + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + user_id (:obj:`int`, optional): Identifier of the user whose reactions will be removed, + if the reactions were added by a user. + actor_chat_id (:obj:`int`, optional): Identifier of the chat whose reactions will be + removed, if the reactions were added by a chat. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "chat_id": chat_id, + "user_id": user_id, + "actor_chat_id": actor_chat_id, + } + + return await self._post( + "deleteAllMessageReactions", + 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: # noqa: ARG002 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -12629,3 +12733,7 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """Alias for :meth:`replace_managed_bot_token`""" savePreparedKeyboardButton = save_prepared_keyboard_button """Alias for :meth:`save_prepared_keyboard_button`""" + deleteMessageReaction = delete_message_reaction + """Alias for :meth:`delete_message_reaction`""" + deleteAllMessageReactions = delete_all_message_reactions + """Alias for :meth:`delete_all_message_reactions`""" diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 764da868e7e..c0164f36270 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -311,6 +311,7 @@ async def leave( async def get_administrators( self, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -334,6 +335,7 @@ async def get_administrators( """ return await self.get_bot().get_chat_administrators( chat_id=self.id, + return_bots=return_bots, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -4049,6 +4051,82 @@ async def set_chat_member_tag( api_kwargs=api_kwargs, ) + async def delete_reaction( + self, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_message_reaction(chat_id=update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + chat_id=self.id, + message_id=message_id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_reactions( + self, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_all_message_reactions( + chat_id=update.effective_chat.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_all_message_reactions`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_all_message_reactions( + chat_id=self.id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + class Chat(_ChatBase): """This object represents a chat. diff --git a/src/telegram/_chatmember.py b/src/telegram/_chatmember.py index be55c8469e8..8ad48616376 100644 --- a/src/telegram/_chatmember.py +++ b/src/telegram/_chatmember.py @@ -517,6 +517,10 @@ class ChatMemberRestricted(ChatMember): can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to + messages. + + .. versionadded:: NEXT.VERSION tag (:obj:`str`, optional): Tag of the member. .. versionadded:: 22.7 @@ -573,6 +577,10 @@ class ChatMemberRestricted(ChatMember): can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to + messages. + + .. versionadded:: NEXT.VERSION tag (:obj:`str`): Optional. Tag of the member. .. versionadded:: 22.7 @@ -586,6 +594,7 @@ class ChatMemberRestricted(ChatMember): "can_invite_users", "can_manage_topics", "can_pin_messages", + "can_react_to_messages", "can_send_audios", "can_send_documents", "can_send_messages", @@ -621,10 +630,17 @@ def __init__( can_send_voice_notes: bool, can_edit_tag: bool, tag: str | None = None, + # tags: NEXT.VERSION + # temporarily optional to make it not breaking + can_react_to_messages: bool | None = None, *, api_kwargs: JSONDict | None = None, ): super().__init__(status=ChatMember.RESTRICTED, user=user, api_kwargs=api_kwargs) + + if can_react_to_messages is None: + raise TypeError("`can_react_to_messages` is required and cannot be None") + with self._unfrozen(): self.is_member: bool = is_member self.can_change_info: bool = can_change_info @@ -643,6 +659,7 @@ def __init__( self.can_send_video_notes: bool = can_send_video_notes self.can_send_voice_notes: bool = can_send_voice_notes self.can_edit_tag: bool = can_edit_tag + self.can_react_to_messages: bool = can_react_to_messages self.tag: str | None = tag diff --git a/src/telegram/_chatpermissions.py b/src/telegram/_chatpermissions.py index 2116f4f4c33..7ca1871473d 100644 --- a/src/telegram/_chatpermissions.py +++ b/src/telegram/_chatpermissions.py @@ -36,7 +36,7 @@ class ChatPermissions(TelegramObject): :attr:`can_change_info`, :attr:`can_invite_users`, :attr:`can_pin_messages`, :attr:`can_send_audios`, :attr:`can_send_documents`, :attr:`can_send_photos`, :attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`, - :attr:`can_manage_topics` and :attr:`can_edit_tag` are equal. + :attr:`can_manage_topics`, :attr:`can_edit_tag`, and :attr:`can_react_to_messages` are equal. .. versionchanged:: 20.0 :attr:`can_manage_topics` is considered as well when comparing objects of @@ -50,6 +50,9 @@ class ChatPermissions(TelegramObject): .. versionchanged:: 22.7 :attr:`can_edit_tag` is considered as well when comparing objects of this type in terms of equality. + .. versionchanged:: NEXT.VERSION + :attr:`can_react_to_messages` is considered as well when comparing objects of + this type in terms of equality. Note: @@ -100,6 +103,10 @@ class ChatPermissions(TelegramObject): tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to react + to messages. If omitted, defaults to the value of :attr:`can_send_messages`. + + .. versionadded:: NEXT.VERSION Attributes: can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text @@ -145,6 +152,10 @@ class ChatPermissions(TelegramObject): tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to react + to messages. If omitted, defaults to the value of :attr:`can_send_messages`. + + .. versionadded:: NEXT.VERSION """ @@ -155,6 +166,7 @@ class ChatPermissions(TelegramObject): "can_invite_users", "can_manage_topics", "can_pin_messages", + "can_react_to_messages", "can_send_audios", "can_send_documents", "can_send_messages", @@ -183,6 +195,7 @@ def __init__( can_send_video_notes: bool | None = None, can_send_voice_notes: bool | None = None, can_edit_tag: bool | None = None, + can_react_to_messages: bool | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -203,6 +216,7 @@ def __init__( self.can_send_video_notes: bool | None = can_send_video_notes self.can_send_voice_notes: bool | None = can_send_voice_notes self.can_edit_tag: bool | None = can_edit_tag + self.can_react_to_messages: bool | None = can_react_to_messages self._id_attrs = ( self.can_send_messages, @@ -220,6 +234,7 @@ def __init__( self.can_send_video_notes, self.can_send_voice_notes, self.can_edit_tag, + self.can_react_to_messages, ) self._freeze() diff --git a/src/telegram/_message.py b/src/telegram/_message.py index 8c6626f50a6..b87b8bdae4b 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -5220,6 +5220,45 @@ async def decline_suggested_post( api_kwargs=api_kwargs, ) + async def delete_reaction( + self, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """Shortcut for:: + + await bot.delete_message_reaction( + chat_id=message.chat_id, + message_id=message.message_id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool` On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + chat_id=self.chat_id, + message_id=self.message_id, + user_id=user_id, + actor_chat_id=actor_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/src/telegram/_user.py b/src/telegram/_user.py index 821f230adb6..a2b53d6fede 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -2761,7 +2761,7 @@ async def replace_token( .. versionadded:: NEXT.VERSION Returns: - :obj:`bool`: On success, :obj:`str` is returned. + :obj:`str`: On success, :obj:`str` is returned. """ return await self.get_bot().replace_managed_bot_token( user_id=self.id, @@ -2771,3 +2771,83 @@ async def replace_token( pool_timeout=pool_timeout, api_kwargs=api_kwargs, ) + + async def delete_reaction( + self, + chat_id: int | str, + message_id: int, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_message_reaction( + user_id=update.effective_user.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + user_id=self.id, + chat_id=chat_id, + message_id=message_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_reactions( + self, + chat_id: int | str, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_all_message_reactions( + user_id=update.effective_user.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_all_message_reactions`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_all_message_reactions( + chat_id=chat_id, + user_id=self.id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) diff --git a/src/telegram/ext/_extbot.py b/src/telegram/ext/_extbot.py index 931115f7beb..b6595fe0e98 100644 --- a/src/telegram/ext/_extbot.py +++ b/src/telegram/ext/_extbot.py @@ -1844,6 +1844,7 @@ async def forward_messages( async def get_chat_administrators( self, chat_id: str | int, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1854,6 +1855,7 @@ async def get_chat_administrators( ) -> tuple[ChatMember, ...]: return await super().get_chat_administrators( chat_id=chat_id, + return_bots=return_bots, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -5593,6 +5595,56 @@ async def save_prepared_keyboard_button( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def delete_message_reaction( + self, + chat_id: int | str, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> bool: + return await super().delete_message_reaction( + chat_id=chat_id, + message_id=message_id, + user_id=user_id, + actor_chat_id=actor_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 delete_all_message_reactions( + self, + chat_id: int | str, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> bool: + return await super().delete_all_message_reactions( + chat_id=chat_id, + user_id=user_id, + actor_chat_id=actor_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), + ) + # updated camelCase aliases getMe = get_me sendMessage = send_message @@ -5762,3 +5814,5 @@ async def save_prepared_keyboard_button( getManagedBotToken = get_managed_bot_token replaceManagedBotToken = replace_managed_bot_token savePreparedKeyboardButton = save_prepared_keyboard_button + deleteMessageReaction = delete_message_reaction + deleteAllMessageReactions = delete_all_message_reactions diff --git a/tests/test_bot.py b/tests/test_bot.py index 2fd77550ecb..15673347538 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2920,6 +2920,26 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): ) assert isinstance(inst, PreparedKeyboardButton) + # Bots cannot delete their own reaction from my testing, so we aren't making a real request + async def test_delete_message_reaction(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("chat_id") == 1234 + assert request_data.parameters.get("message_id") == 12 + assert request_data.parameters.get("user_id") == 3432 + assert request_data.parameters.get("actor_chat_id") == 1232 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.delete_message_reaction(1234, 12, 3432, 1232) + + async def test_delete_all_message_reactions(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("chat_id") == 1234 + assert request_data.parameters.get("user_id") == 3432 + assert request_data.parameters.get("actor_chat_id") == 1232 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.delete_all_message_reactions(1234, 3432, 1232) + class TestBotWithRequest: """ @@ -3705,11 +3725,15 @@ async def test_get_chat(self, bot, super_group_id): assert cfi.id == int(super_group_id) async def test_get_chat_administrators(self, bot, channel_id): - admins = await bot.get_chat_administrators(channel_id) + admins = await bot.get_chat_administrators(channel_id, return_bots=True) assert isinstance(admins, tuple) + bots_found = 0 for a in admins: assert a.status in ("administrator", "creator") + if a.user.is_bot: + bots_found += 1 + assert bots_found > 1 # will be False if return_bots=False async def test_get_chat_member_count(self, bot, channel_id): count = await bot.get_chat_member_count(channel_id) diff --git a/tests/test_chat.py b/tests/test_chat.py index 8d1d2db23ce..b6159896200 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -1537,6 +1537,68 @@ async def make_assertion(*_, **kwargs): active_period=3600, ) + async def test_instance_method_delete_reaction(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["message_id"] == 321 + and kwargs["user_id"] == 123 + and kwargs["actor_chat_id"] == 222 + ) + + assert check_shortcut_signature( + Chat.delete_reaction, + Bot.delete_message_reaction, + [ + "chat_id", + ], + additional_kwargs=[], + ) + assert await check_shortcut_call( + chat.delete_reaction, + chat.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.delete_reaction, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "delete_message_reaction", make_assertion) + assert await chat.delete_reaction( + user_id=123, + message_id=321, + actor_chat_id=222, + ) + + async def test_instance_method_delete_all_reactions(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["user_id"] == 123 + and kwargs["actor_chat_id"] == 222 + ) + + assert check_shortcut_signature( + Chat.delete_all_reactions, + Bot.delete_all_message_reactions, + [ + "chat_id", + ], + additional_kwargs=[], + ) + assert await check_shortcut_call( + chat.delete_all_reactions, + chat.get_bot(), + "delete_all_message_reactions", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.delete_all_reactions, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "delete_all_message_reactions", make_assertion) + assert await chat.delete_all_reactions( + user_id=123, + actor_chat_id=222, + ) + async def test_instance_method_get_gifts(self, monkeypatch, chat): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == chat.id diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py index 7bc4a2979f6..ead273bdbd2 100644 --- a/tests/test_chatmember.py +++ b/tests/test_chatmember.py @@ -61,6 +61,7 @@ class ChatMemberTestBase: can_pin_messages = True can_post_stories = True can_edit_stories = True + can_react_to_messages = True can_delete_stories = True can_manage_topics = True until_date = dtm.datetime.now(UTC).replace(microsecond=0) @@ -576,6 +577,7 @@ def chat_member_restricted(): is_member=TestChatMemberRestrictedWithoutRequest.is_member, until_date=TestChatMemberRestrictedWithoutRequest.until_date, can_edit_tag=TestChatMemberRestrictedWithoutRequest.can_edit_tag, + can_react_to_messages=TestChatMemberRestrictedWithoutRequest.can_react_to_messages, tag=TestChatMemberRestrictedWithoutRequest.tag, ) @@ -609,6 +611,7 @@ def test_de_json(self, offline_bot): "is_member": self.is_member, "until_date": to_timestamp(self.until_date), "can_edit_tag": self.can_edit_tag, + "can_react_to_messages": self.can_react_to_messages, "tag": self.tag, # legacy argument "can_send_media_messages": False, @@ -636,6 +639,7 @@ def test_de_json(self, offline_bot): assert chat_member.is_member == self.is_member assert chat_member.until_date == self.until_date assert chat_member.can_edit_tag == self.can_edit_tag + assert chat_member.can_react_to_messages == self.can_react_to_messages assert chat_member.tag == self.tag def test_de_json_localization(self, tz_bot, offline_bot, raw_bot, chat_member_restricted): @@ -676,9 +680,22 @@ def test_to_dict(self, chat_member_restricted): "is_member": chat_member_restricted.is_member, "until_date": to_timestamp(chat_member_restricted.until_date), "can_edit_tag": chat_member_restricted.can_edit_tag, + "can_react_to_messages": chat_member_restricted.can_react_to_messages, "tag": chat_member_restricted.tag, } + def test_can_react_to_messages_raises(self, chat_member_restricted): + with pytest.raises( + TypeError, match="`can_react_to_messages` is required and cannot be None" + ): + ChatMemberRestricted( + *[ + getattr(chat_member_restricted, k) + for k in chat_member_restricted.__slots__ + if k != "can_react_to_messages" + ] + ) + def test_equality(self, chat_member_restricted): a = chat_member_restricted b = deepcopy(chat_member_restricted) @@ -701,6 +718,7 @@ def test_equality(self, chat_member_restricted): False, False, False, + False, "tag", ) d = Dice(5, "test") diff --git a/tests/test_chatpermissions.py b/tests/test_chatpermissions.py index 0baee3f89d7..43aeabcf537 100644 --- a/tests/test_chatpermissions.py +++ b/tests/test_chatpermissions.py @@ -41,6 +41,7 @@ def chat_permissions(): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) @@ -60,6 +61,7 @@ class ChatPermissionsTestBase: can_send_video_notes = False can_send_voice_notes = None can_edit_tag = None + can_react_to_messages = True class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase): @@ -86,6 +88,7 @@ def test_de_json(self, offline_bot): "can_send_video_notes": self.can_send_video_notes, "can_send_voice_notes": self.can_send_voice_notes, "can_edit_tag": self.can_edit_tag, + "can_react_to_messages": self.can_react_to_messages, } permissions = ChatPermissions.de_json(json_dict, offline_bot) assert permissions.api_kwargs == {"can_send_media_messages": "can_send_media_messages"} @@ -105,6 +108,7 @@ def test_de_json(self, offline_bot): assert permissions.can_send_video_notes == self.can_send_video_notes assert permissions.can_send_voice_notes == self.can_send_voice_notes assert permissions.can_edit_tag == self.can_edit_tag + assert permissions.can_react_to_messages == self.can_react_to_messages def test_to_dict(self, chat_permissions): permissions_dict = chat_permissions.to_dict() @@ -130,6 +134,7 @@ def test_to_dict(self, chat_permissions): assert permissions_dict["can_send_video_notes"] == chat_permissions.can_send_video_notes assert permissions_dict["can_send_voice_notes"] == chat_permissions.can_send_voice_notes assert permissions_dict["can_edit_tag"] == chat_permissions.can_edit_tag + assert permissions_dict["can_react_to_messages"] == chat_permissions.can_react_to_messages def test_equality(self): a = ChatPermissions( @@ -159,6 +164,7 @@ def test_equality(self): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) f = ChatPermissions( can_send_messages=True, @@ -171,6 +177,7 @@ def test_equality(self): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) assert a == b diff --git a/tests/test_message.py b/tests/test_message.py index 958c59b3109..700491f2752 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -3419,3 +3419,29 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(message.get_bot(), "decline_suggested_post", make_assertion) assert await message.decline_suggested_post(comment="some comment") + + async def test_delete_reaction(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == message.chat_id + and kwargs["message_id"] == message.message_id + and kwargs["user_id"] == 23 + and kwargs["actor_chat_id"] == 12 + ) + + assert check_shortcut_signature( + Message.delete_reaction, + Bot.delete_message_reaction, + ["chat_id", "message_id"], + [], + ) + assert await check_shortcut_call( + message.delete_reaction, + message.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["chat_id", "message_id"], + ) + assert await check_defaults_handling(message.delete_reaction, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "delete_message_reaction", make_assertion) + assert await message.delete_reaction(user_id=23, actor_chat_id=12) diff --git a/tests/test_user.py b/tests/test_user.py index 24f8ffe8c8d..4d6d35755e5 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -926,3 +926,48 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "replace_managed_bot_token", make_assertion) assert await user.replace_token() + + async def test_instance_method_delete_reaction(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["user_id"] == user.id + and kwargs["chat_id"] == 1234 + and kwargs["message_id"] == 123 + and kwargs["actor_chat_id"] == 42 + ) + + assert check_shortcut_signature( + user.delete_reaction, Bot.delete_message_reaction, ["user_id"], [] + ) + assert await check_shortcut_call( + user.delete_reaction, + user.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["user_id"], + ) + assert await check_defaults_handling(user.delete_reaction, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "delete_message_reaction", make_assertion) + assert await user.delete_reaction(chat_id=1234, message_id=123, actor_chat_id=42) + + async def test_instance_method_delete_all_reactions(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["user_id"] == user.id + and kwargs["chat_id"] == 1234 + and kwargs["actor_chat_id"] == 42 + ) + + assert check_shortcut_signature( + user.delete_all_reactions, Bot.delete_all_message_reactions, ["user_id"], [] + ) + assert await check_shortcut_call( + user.delete_all_reactions, + user.get_bot(), + "delete_all_message_reactions", + shortcut_kwargs=["user_id"], + ) + assert await check_defaults_handling(user.delete_all_reactions, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "delete_all_message_reactions", make_assertion) + assert await user.delete_all_reactions(chat_id=1234, actor_chat_id=42)