From d4fda6a8f1c2d28a7db1bda1ff8005a52ef8b701 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Thu, 9 May 2024 03:07:06 +0300 Subject: [PATCH 01/12] Add `ChatMemberUpdated.via_join_request` and tests - [x] Added the field via_join_request to the class ChatMemberUpdated. --- telegram/_chatmemberupdated.py | 13 +++++++++++++ tests/test_chatmemberupdated.py | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/telegram/_chatmemberupdated.py b/telegram/_chatmemberupdated.py index 7d5ee556be7..41dbb884492 100644 --- a/telegram/_chatmemberupdated.py +++ b/telegram/_chatmemberupdated.py @@ -63,6 +63,11 @@ class ChatMemberUpdated(TelegramObject): chat via a chat folder invite link .. versionadded:: 20.3 + via_join_request (:obj:`bool`, optional): :obj:`True`, if the user joined the chat after + sending a direct join request without using an invite link and being approved by + an administrator + + .. versionadded:: NEXT.VERSION Attributes: chat (:class:`telegram.Chat`): Chat the user belongs to. @@ -80,6 +85,11 @@ class ChatMemberUpdated(TelegramObject): chat via a chat folder invite link .. versionadded:: 20.3 + via_join_request (:obj:`bool`): Optional. :obj:`True`, if the user joined the chat after + sending a direct join request without using an invite link and being approved + by an administrator + + .. versionadded:: NEXT.VERSION """ @@ -91,6 +101,7 @@ class ChatMemberUpdated(TelegramObject): "new_chat_member", "old_chat_member", "via_chat_folder_invite_link", + "via_join_request", ) def __init__( @@ -102,6 +113,7 @@ def __init__( new_chat_member: ChatMember, invite_link: Optional[ChatInviteLink] = None, via_chat_folder_invite_link: Optional[bool] = None, + via_join_request: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -116,6 +128,7 @@ def __init__( # Optionals self.invite_link: Optional[ChatInviteLink] = invite_link + self.via_join_request: Optional[bool] = via_join_request self._id_attrs = ( self.chat, diff --git a/tests/test_chatmemberupdated.py b/tests/test_chatmemberupdated.py index 0cf5e58101c..0efbcd8d0ab 100644 --- a/tests/test_chatmemberupdated.py +++ b/tests/test_chatmemberupdated.py @@ -82,7 +82,9 @@ def invite_link(user): @pytest.fixture(scope="module") def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time): - return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link, True) + return ChatMemberUpdated( + chat, user, time, old_chat_member, new_chat_member, invite_link, True, True + ) class TestChatMemberUpdatedBase: @@ -129,6 +131,7 @@ def test_de_json_all_args( "new_chat_member": new_chat_member.to_dict(), "invite_link": invite_link.to_dict(), "via_chat_folder_invite_link": True, + "via_join_request": True, } chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot) @@ -142,6 +145,7 @@ def test_de_json_all_args( assert chat_member_updated.new_chat_member == new_chat_member assert chat_member_updated.invite_link == invite_link assert chat_member_updated.via_chat_folder_invite_link is True + assert chat_member_updated.via_join_request is True def test_de_json_localization( self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link @@ -188,6 +192,7 @@ def test_to_dict(self, chat_member_updated): chat_member_updated_dict["via_chat_folder_invite_link"] == chat_member_updated.via_chat_folder_invite_link ) + assert chat_member_updated_dict["via_join_request"] == chat_member_updated.via_join_request def test_equality(self, time, old_chat_member, new_chat_member, invite_link): a = ChatMemberUpdated( From 2e94c7b3215e5b705c6c7ea1a22f0a1e6f637f99 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Thu, 9 May 2024 04:37:25 +0300 Subject: [PATCH 02/12] Add `edit_message_live_location.live_period` and tests. - [x] Added the parameter live_period to the method editMessageLiveLocation. --- telegram/_bot.py | 10 ++++++++++ telegram/_callbackquery.py | 3 +++ telegram/_message.py | 2 ++ telegram/ext/_extbot.py | 2 ++ tests/_files/test_location.py | 6 +++++- tests/test_callbackquery.py | 9 ++++++--- tests/test_message.py | 5 +++-- 7 files changed, 31 insertions(+), 6 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 8bb4af23de7..937d186ff01 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -2720,6 +2720,7 @@ async def edit_message_live_location( horizontal_accuracy: Optional[float] = None, heading: Optional[int] = None, proximity_alert_radius: Optional[int] = None, + live_period: Optional[int] = None, *, location: Optional[Location] = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2758,6 +2759,14 @@ async def edit_message_live_location( if specified. reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new inline keyboard. + live_period (:obj:`int`, optional): New period in seconds during which the location + can be updated, starting from the message send date. If `0x7FFFFFFF` is specified, + then the location can be updated forever. Otherwise, the new value must not exceed + the current live_period by more than a day, and the live location expiration date + must remain within the next 90 days. If not specified, then `live_period` + remains unchanged + + .. versionadded:: NEXT.VERSION. Keyword Args: location (:class:`telegram.Location`, optional): The location to send. @@ -2790,6 +2799,7 @@ async def edit_message_live_location( "horizontal_accuracy": horizontal_accuracy, "heading": heading, "proximity_alert_radius": proximity_alert_radius, + "live_period": live_period, } return await self._send_message( diff --git a/telegram/_callbackquery.py b/telegram/_callbackquery.py index 8fbd0013a4e..3df7089c997 100644 --- a/telegram/_callbackquery.py +++ b/telegram/_callbackquery.py @@ -461,6 +461,7 @@ async def edit_message_live_location( horizontal_accuracy: Optional[float] = None, heading: Optional[int] = None, proximity_alert_radius: Optional[int] = None, + live_period: Optional[int] = None, *, location: Optional[Location] = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -509,6 +510,7 @@ async def edit_message_live_location( horizontal_accuracy=horizontal_accuracy, heading=heading, proximity_alert_radius=proximity_alert_radius, + live_period=live_period, chat_id=None, message_id=None, ) @@ -525,6 +527,7 @@ async def edit_message_live_location( horizontal_accuracy=horizontal_accuracy, heading=heading, proximity_alert_radius=proximity_alert_radius, + live_period=live_period, ) async def stop_message_live_location( diff --git a/telegram/_message.py b/telegram/_message.py index 87ecdd300f3..f48418a8f8f 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -3653,6 +3653,7 @@ async def edit_live_location( horizontal_accuracy: Optional[float] = None, heading: Optional[int] = None, proximity_alert_radius: Optional[int] = None, + live_period: Optional[int] = None, *, location: Optional[Location] = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3694,6 +3695,7 @@ async def edit_live_location( horizontal_accuracy=horizontal_accuracy, heading=heading, proximity_alert_radius=proximity_alert_radius, + live_period=live_period, inline_message_id=None, ) diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 7b5649ebea3..843aebec2a4 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -1522,6 +1522,7 @@ async def edit_message_live_location( horizontal_accuracy: Optional[float] = None, heading: Optional[int] = None, proximity_alert_radius: Optional[int] = None, + live_period: Optional[int] = None, *, location: Optional[Location] = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1541,6 +1542,7 @@ async def edit_message_live_location( horizontal_accuracy=horizontal_accuracy, heading=heading, proximity_alert_radius=proximity_alert_radius, + live_period=live_period, location=location, read_timeout=read_timeout, write_timeout=write_timeout, diff --git a/tests/_files/test_location.py b/tests/_files/test_location.py index aec282ccdcd..5b94df4916b 100644 --- a/tests/_files/test_location.py +++ b/tests/_files/test_location.py @@ -124,7 +124,8 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): ha = data["horizontal_accuracy"] == "50" heading = data["heading"] == "90" prox_alert = data["proximity_alert_radius"] == "1000" - return lat and lon and id_ and ha and heading and prox_alert + live = data["live_period"] == "900" + return lat and lon and id_ and ha and heading and prox_alert and live monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.edit_message_live_location( @@ -133,6 +134,7 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): horizontal_accuracy=50, heading=90, proximity_alert_radius=1000, + live_period=900, ) # TODO: Needs improvement with in inline sent live location. @@ -262,6 +264,7 @@ async def test_send_live_location(self, bot, chat_id): horizontal_accuracy=30, heading=10, proximity_alert_radius=500, + live_period=200, ) assert pytest.approx(message2.location.latitude, rel=1e-5) == 52.223098 @@ -269,6 +272,7 @@ async def test_send_live_location(self, bot, chat_id): assert message2.location.horizontal_accuracy == 30 assert message2.location.heading == 10 assert message2.location.proximity_alert_radius == 500 + assert message2.location.live_period == 200 await bot.stop_message_live_location(message.chat_id, message.message_id) with pytest.raises(BadRequest, match="Message can't be edited"): diff --git a/tests/test_callbackquery.py b/tests/test_callbackquery.py index 1db05e4b973..66dc6856924 100644 --- a/tests/test_callbackquery.py +++ b/tests/test_callbackquery.py @@ -301,8 +301,9 @@ async def test_edit_message_live_location(self, monkeypatch, callback_query): async def make_assertion(*_, **kwargs): latitude = kwargs.get("latitude") == 1 longitude = kwargs.get("longitude") == 2 + live = kwargs.get("live_period") == 900 ids = self.check_passed_ids(callback_query, kwargs) - return ids and latitude and longitude + return ids and latitude and longitude and live assert check_shortcut_signature( CallbackQuery.edit_message_live_location, @@ -322,8 +323,10 @@ async def make_assertion(*_, **kwargs): ) monkeypatch.setattr(callback_query.get_bot(), "edit_message_live_location", make_assertion) - assert await callback_query.edit_message_live_location(latitude=1, longitude=2) - assert await callback_query.edit_message_live_location(1, 2) + assert await callback_query.edit_message_live_location( + latitude=1, longitude=2, live_period=900 + ) + assert await callback_query.edit_message_live_location(1, 2, live_period=900) async def test_stop_message_live_location(self, monkeypatch, callback_query): if isinstance(callback_query.message, InaccessibleMessage): diff --git a/tests/test_message.py b/tests/test_message.py index e70b8f0668f..440ec50651c 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -2414,7 +2414,8 @@ async def make_assertion(*_, **kwargs): message_id = kwargs["message_id"] == message.message_id latitude = kwargs["latitude"] == 1 longitude = kwargs["longitude"] == 2 - return chat_id and message_id and longitude and latitude + live = kwargs["live_period"] == 900 + return chat_id and message_id and longitude and latitude and live assert check_shortcut_signature( Message.edit_live_location, @@ -2432,7 +2433,7 @@ async def make_assertion(*_, **kwargs): assert await check_defaults_handling(message.edit_live_location, message.get_bot()) monkeypatch.setattr(message.get_bot(), "edit_message_live_location", make_assertion) - assert await message.edit_live_location(latitude=1, longitude=2) + assert await message.edit_live_location(latitude=1, longitude=2, live_period=900) async def test_stop_live_location(self, monkeypatch, message): async def make_assertion(*_, **kwargs): From 69b0c38da681f8890977b758bfdb8c08c7e1f1d2 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Thu, 9 May 2024 14:04:17 +0300 Subject: [PATCH 03/12] Add Backgrounds and tests. - [x] Added the classes `ChatBackground`, `BackgroundType`, `BackgroundFill` and the field `chat_background_set` of type `ChatBackground` to the class Message, describing service messages about background changes --- docs/source/telegram.at-tree.rst | 10 + docs/source/telegram.backgroundfill.rst | 8 + ...elegram.backgroundfillfreeformgradient.rst | 8 + .../telegram.backgroundfillgradient.rst | 8 + docs/source/telegram.backgroundfillsolid.rst | 8 + docs/source/telegram.backgroundtype.rst | 8 + .../telegram.backgroundtypechattheme.rst | 8 + docs/source/telegram.backgroundtypefill.rst | 8 + .../source/telegram.backgroundtypepattern.rst | 8 + .../telegram.backgroundtypewallpaper.rst | 8 + docs/source/telegram.chatbackground.rst | 8 + telegram/__init__.py | 22 + telegram/_chatbackground.py | 494 ++++++++++++++++++ telegram/_message.py | 15 + telegram/constants.py | 43 ++ tests/test_chatbackground.py | 345 ++++++++++++ tests/test_message.py | 4 + 17 files changed, 1013 insertions(+) create mode 100644 docs/source/telegram.backgroundfill.rst create mode 100644 docs/source/telegram.backgroundfillfreeformgradient.rst create mode 100644 docs/source/telegram.backgroundfillgradient.rst create mode 100644 docs/source/telegram.backgroundfillsolid.rst create mode 100644 docs/source/telegram.backgroundtype.rst create mode 100644 docs/source/telegram.backgroundtypechattheme.rst create mode 100644 docs/source/telegram.backgroundtypefill.rst create mode 100644 docs/source/telegram.backgroundtypepattern.rst create mode 100644 docs/source/telegram.backgroundtypewallpaper.rst create mode 100644 docs/source/telegram.chatbackground.rst create mode 100644 telegram/_chatbackground.py create mode 100644 tests/test_chatbackground.py diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 3d78292588a..55478148381 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -28,6 +28,16 @@ Available Types telegram.callbackquery telegram.chat telegram.chatadministratorrights + telegram.chatbackground + telegram.backgroundtype + telegram.backgroundtypefill + telegram.backgroundtypewallpaper + telegram.backgroundtypepattern + telegram.backgroundtypechattheme + telegram.backgroundfill + telegram.backgroundfillsolid + telegram.backgroundfillgradient + telegram.backgroundfillfreeformgradient telegram.chatboost telegram.chatboostadded telegram.chatboostremoved diff --git a/docs/source/telegram.backgroundfill.rst b/docs/source/telegram.backgroundfill.rst new file mode 100644 index 00000000000..5f7e68f9f08 --- /dev/null +++ b/docs/source/telegram.backgroundfill.rst @@ -0,0 +1,8 @@ +BackgroundFill +======================= + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundFill + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundfillfreeformgradient.rst b/docs/source/telegram.backgroundfillfreeformgradient.rst new file mode 100644 index 00000000000..24ddffb70e5 --- /dev/null +++ b/docs/source/telegram.backgroundfillfreeformgradient.rst @@ -0,0 +1,8 @@ +BackgroundFillFreeformGradient +============================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundFillFreeformGradient + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundfillgradient.rst b/docs/source/telegram.backgroundfillgradient.rst new file mode 100644 index 00000000000..9955c6f1d86 --- /dev/null +++ b/docs/source/telegram.backgroundfillgradient.rst @@ -0,0 +1,8 @@ +BackgroundFillGradient +====================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundFillGradient + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundfillsolid.rst b/docs/source/telegram.backgroundfillsolid.rst new file mode 100644 index 00000000000..27be28d520b --- /dev/null +++ b/docs/source/telegram.backgroundfillsolid.rst @@ -0,0 +1,8 @@ +BackgroundFillSolid +=================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundFillSolid + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundtype.rst b/docs/source/telegram.backgroundtype.rst new file mode 100644 index 00000000000..f890ca12daa --- /dev/null +++ b/docs/source/telegram.backgroundtype.rst @@ -0,0 +1,8 @@ +BackgroundType +============== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundType + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundtypechattheme.rst b/docs/source/telegram.backgroundtypechattheme.rst new file mode 100644 index 00000000000..d192d4aae34 --- /dev/null +++ b/docs/source/telegram.backgroundtypechattheme.rst @@ -0,0 +1,8 @@ +BackgroundTypeChatTheme +======================= + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundTypeChatTheme + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundtypefill.rst b/docs/source/telegram.backgroundtypefill.rst new file mode 100644 index 00000000000..5c0373c4e3d --- /dev/null +++ b/docs/source/telegram.backgroundtypefill.rst @@ -0,0 +1,8 @@ +BackgroundTypeFill +================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundTypeFill + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundtypepattern.rst b/docs/source/telegram.backgroundtypepattern.rst new file mode 100644 index 00000000000..763a6e69c8d --- /dev/null +++ b/docs/source/telegram.backgroundtypepattern.rst @@ -0,0 +1,8 @@ +BackgroundTypePattern +===================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundTypePattern + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.backgroundtypewallpaper.rst b/docs/source/telegram.backgroundtypewallpaper.rst new file mode 100644 index 00000000000..3852cdab592 --- /dev/null +++ b/docs/source/telegram.backgroundtypewallpaper.rst @@ -0,0 +1,8 @@ +BackgroundTypeWallpaper +======================= + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.BackgroundTypeWallpaper + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.chatbackground.rst b/docs/source/telegram.chatbackground.rst new file mode 100644 index 00000000000..07e28d78b3e --- /dev/null +++ b/docs/source/telegram.chatbackground.rst @@ -0,0 +1,8 @@ +ChatBackground +============== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.ChatBackground + :members: + :show-inheritance: \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index 304425c4d61..cfc70f9fc78 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -22,6 +22,15 @@ __all__ = ( "Animation", "Audio", + "BackgroundFill", + "BackgroundFillFreeformGradient", + "BackgroundFillGradient", + "BackgroundFillSolid", + "BackgroundType", + "BackgroundTypeChatTheme", + "BackgroundTypeFill", + "BackgroundTypePattern", + "BackgroundTypeWallpaper", "Birthdate", "Bot", "BotCommand", @@ -46,6 +55,7 @@ "CallbackQuery", "Chat", "ChatAdministratorRights", + "ChatBackground", "ChatBoost", "ChatBoostAdded", "ChatBoostRemoved", @@ -258,6 +268,18 @@ from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights +from ._chatbackground import ( + BackgroundFill, + BackgroundFillFreeformGradient, + BackgroundFillGradient, + BackgroundFillSolid, + BackgroundType, + BackgroundTypeChatTheme, + BackgroundTypeFill, + BackgroundTypePattern, + BackgroundTypeWallpaper, + ChatBackground, +) from ._chatboost import ( ChatBoost, ChatBoostAdded, diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py new file mode 100644 index 00000000000..4630d4b8477 --- /dev/null +++ b/telegram/_chatbackground.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# 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 chat backgrounds.""" +from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Type + +from telegram import constants +from telegram._files.document import Document +from telegram._telegramobject import TelegramObject +from telegram._utils import enum +from telegram._utils.argumentparsing import parse_sequence_arg +from telegram._utils.types import JSONDict + +if TYPE_CHECKING: + from telegram import Bot + + +class BackgroundFill(TelegramObject): + """Base class for Telegram BackgroundFill Objects. It can be one of: + + * :class:`telegram.BackgroundFillSolid` + * :class:`telegram.BackgroundFillGradient` + * :class:`telegram.BackgroundFillFreeformGradient` + + .. versionadded:: NEXT.VERSION + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`type` is equal. + + Args: + type (:obj:`str`): Type of the background fill. Can be one of: + :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` + or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. + + Attributes: + type (:obj:`str`): Type of the background fill. Can be one of: + :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` + or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. + + """ + + __slots__ = ("type",) + + SOLID: Final[constants.BackgroundFill] = constants.BackgroundFill.SOLID + """:const:`telegram.constants.BackgroundFill.SOLID`""" + GRADIENT: Final[constants.BackgroundFill] = constants.BackgroundFill.GRADIENT + """:const:`telegram.constants.BackgroundFill.GRADIENT`""" + FREEFORM_GRADIENT: Final[constants.BackgroundFill] = constants.BackgroundFill.FREEFORM_GRADIENT + """:const:`telegram.constants.BackgroundFill.FREEFORM_GRADIENT`""" + + def __init__( + self, + type: str, # pylint: disable=redefined-builtin + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required by all subclasses + self.type: str = enum.get_member(constants.BackgroundFill, type, type) + + self._id_attrs = (self.type,) + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundFill"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + _class_mapping: Dict[str, Type[BackgroundFill]] = { + cls.SOLID: BackgroundFillSolid, + cls.GRADIENT: BackgroundFillGradient, + cls.FREEFORM_GRADIENT: BackgroundFillFreeformGradient, + } + + if cls is BackgroundFill and data.get("type") in _class_mapping: + return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) + + return super().de_json(data=data, bot=bot) + + +class BackgroundFillSolid(BackgroundFill): + """ + The background is filled using the selected color. + + .. versionadded:: NEXT.VERSION + + Args: + color (:obj:`int`): The color of the background fill in the `RGB24` format. + + Attributes: + type (:obj:`str`): Type of the background fill. Always + :attr:`~telegram.BackgroundFill.SOLID`. + color (:obj:`int`): The color of the background fill in the `RGB24` format. + """ + + __slots__ = ("color",) + + def __init__( + self, + color: int, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.SOLID, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.color: int = color + + +class BackgroundFillGradient(BackgroundFill): + """ + The background is a gradient fill. + + .. versionadded:: NEXT.VERSION + + Args: + top_color (:obj:`int`): Top color of the gradient in the `RGB24` format. + bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. + rotation_angle (:obj:`int`): Clockwise rotation angle of the background + fill in degrees; `0-359`. + + Attributes: + type (:obj:`str`): Type of the background fill. Always + :attr:`~telegram.BackgroundFill.GRADIENT`. + top_color (:obj:`int`): Top color of the gradient in the `RGB24` format. + bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. + rotation_angle (:obj:`int`): Clockwise rotation angle of the background + fill in degrees; `0-359`. + """ + + __slots__ = ("bottom_color", "rotation_angle", "top_color") + + def __init__( + self, + top_color: int, + bottom_color: int, + rotation_angle: int, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.GRADIENT, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.top_color: int = top_color + self.bottom_color: int = bottom_color + self.rotation_angle: int = rotation_angle + + +class BackgroundFillFreeformGradient(BackgroundFill): + """ + The background is a freeform gradient that rotates after every message in the chat. + + .. versionadded:: NEXT.VERSION + + Args: + colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to + generate the freeform gradient in the `RGB24` format + + Attributes: + type (:obj:`str`): Type of the background fill. Always + :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. + colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to + generate the freeform gradient in the `RGB24` format + """ + + __slots__ = ("colors",) + + def __init__( + self, + colors: Sequence[int], + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.FREEFORM_GRADIENT, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.colors: Sequence[int] = parse_sequence_arg(colors) + + +class BackgroundType(TelegramObject): + """Base class for Telegram BackgroundType Objects. It can be one of: + + * :class:`telegram.BackgroundTypeFill` + * :class:`telegram.BackgroundTypeWallpaper` + * :class:`telegram.BackgroundTypePattern` + * :class:`telegram.BackgroundTypeChatTheme`. + + .. versionadded:: NEXT.VERSION + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`type` is equal. + + Args: + type (:obj:`str`): Type of the background. Can be one of: + :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` + :attr:`~telegram.BackgroundType.PATTERN` or + :attr:`~telegram.BackgroundType.CHAT_THEME`. + + Attributes: + type (:obj:`str`): Type of the background. Can be one of: + :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` + :attr:`~telegram.BackgroundType.PATTERN` or + :attr:`~telegram.BackgroundType.CHAT_THEME`. + + """ + + __slots__ = ("type",) + + FILL: Final[constants.BackgroundType] = constants.BackgroundType.FILL + """:const:`telegram.constants.BackgroundType.FILL`""" + WALLPAPER: Final[constants.BackgroundType] = constants.BackgroundType.WALLPAPER + """:const:`telegram.constants.BackgroundType.WALLPAPER`""" + PATTERN: Final[constants.BackgroundType] = constants.BackgroundType.PATTERN + """:const:`telegram.constants.BackgroundType.PATTERN`""" + CHAT_THEME: Final[constants.BackgroundType] = constants.BackgroundType.CHAT_THEME + """:const:`telegram.constants.BackgroundType.CHAT_THEME`""" + + def __init__( + self, + type: str, # pylint: disable=redefined-builtin + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required by all subclasses + self.type: str = enum.get_member(constants.BackgroundType, type, type) + + self._id_attrs = (self.type,) + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundType"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + _class_mapping: Dict[str, Type[BackgroundType]] = { + cls.FILL: BackgroundTypeFill, + cls.WALLPAPER: BackgroundTypeWallpaper, + cls.PATTERN: BackgroundTypePattern, + cls.CHAT_THEME: BackgroundTypeChatTheme, + } + + if cls is BackgroundType and data.get("type") in _class_mapping: + return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) + + if "fill" in data: + data["fill"] = BackgroundFill.de_json(data.get("fill"), bot) + + if "document" in data: + data["document"] = Document.de_json(data.get("document"), bot) + + return super().de_json(data=data, bot=bot) + + +class BackgroundTypeFill(BackgroundType): + """ + The background is automatically filled based on the selected colors. + + .. versionadded:: NEXT.VERSION + + Args: + fill (:obj:`telegram.BackgroundFill`): The background fill. + dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a + percentage; `0-100`. + + Attributes: + type (:obj:`str`): Type of the background. Always + :attr:`~telegram.BackgroundType.FILL`. + fill (:obj:`telegram.BackgroundFill`): The background fill. + dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a + percentage; `0-100`. + """ + + __slots__ = ("dark_theme_dimming", "fill") + + def __init__( + self, + fill: BackgroundFill, + dark_theme_dimming: int, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.FILL, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.fill: BackgroundFill = fill + self.dark_theme_dimming: int = dark_theme_dimming + + +class BackgroundTypeWallpaper(BackgroundType): + """ + The background is a wallpaper in the `JPEG` format. + + .. versionadded:: NEXT.VERSION + + Args: + document (:obj:`telegram.Document`): Document with the wallpaper + dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a + percentage; `0-100` + is_blurred (:obj:`bool`, optional): :obj:`True`, if the wallpaper is downscaled to fit + in a `450x450` square and then box-blurred with radius `12` + is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly + when the device is tilted + + Attributes: + type (:obj:`str`): Type of the background. Always + :attr:`~telegram.BackgroundType.WALLPAPER`. + document (:obj:`telegram.Document`): Document with the wallpaper + dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a + percentage; `0-100` + is_blurred (:obj:`bool`): Optional. :obj:`True`, if the wallpaper is downscaled to fit + in a `450x450` square and then box-blurred with radius `12` + is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly + when the device is tilted + """ + + __slots__ = ("dark_theme_dimming", "document", "is_blurred", "is_moving") + + def __init__( + self, + document: Document, + dark_theme_dimming: int, + is_blurred: Optional[bool] = None, + is_moving: Optional[bool] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.WALLPAPER, api_kwargs=api_kwargs) + + with self._unfrozen(): + # Required + self.document: Document = document + self.dark_theme_dimming: int = dark_theme_dimming + # Optionals + self.is_blurred: Optional[bool] = is_blurred + self.is_moving: Optional[bool] = is_moving + + +class BackgroundTypePattern(BackgroundType): + """ + The background is a `PNG` or `TGV` (gzipped subset of `SVG` with `MIME` type + `"application/x-tgwallpattern"`) pattern to be combined with the background fill + chosen by the user. + + .. versionadded:: NEXT.VERSION + + Args: + document (:obj:`telegram.Document`): Document with the pattern. + fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with + the pattern. + intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled + background; `0-100`. + is_inverted (:obj:`int`, optional): :obj:`True`, if the background fill must be applied + only to the pattern itself. All other pixels are black in this case. For dark + themes only. + is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly + when the device is tilted. + + Attributes: + type (:obj:`str`): Type of the background. Always + :attr:`~telegram.BackgroundType.PATTERN`. + document (:obj:`telegram.Document`): Document with the pattern. + fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with + the pattern. + intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled + background; `0-100`. + is_inverted (:obj:`int`): Optional. :obj:`True`, if the background fill must be applied + only to the pattern itself. All other pixels are black in this case. For dark + themes only. + is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly + when the device is tilted. + """ + + __slots__ = ( + "document", + "fill", + "intensity", + "is_inverted", + "is_moving", + ) + + def __init__( + self, + document: Document, + fill: BackgroundFill, + intensity: int, + is_inverted: Optional[bool] = None, + is_moving: Optional[bool] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.PATTERN, api_kwargs=api_kwargs) + + with self._unfrozen(): + # Required + self.document: Document = document + self.fill: BackgroundFill = fill + self.intensity: int = intensity + # Optionals + self.is_inverted: Optional[bool] = is_inverted + self.is_moving: Optional[bool] = is_moving + + +class BackgroundTypeChatTheme(BackgroundType): + """ + The background is taken directly from a built-in chat theme. + + .. versionadded:: NEXT.VERSION + + Args: + theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji. + + Attributes: + type (:obj:`str`): Type of the background. Always + :attr:`~telegram.BackgroundType.CHAT_THEME`. + theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji. + """ + + __slots__ = ("theme_name",) + + def __init__( + self, + theme_name: str, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(type=self.CHAT_THEME, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.theme_name: str = theme_name + + +class ChatBackground(TelegramObject): + """ + This object represents a chat background. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`type` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + type (:obj:`telegram.BackgroundType`): Type of the background. + + Attributes: + type (:obj:`telegram.BackgroundType`): Type of the background. + """ + + __slots__ = ("type",) + + def __init__( + self, + type: BackgroundType, # pylint: disable=redefined-builtin + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.type: BackgroundType = type + + self._id_attrs = (self.type,) + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatBackground"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["type"] = BackgroundType.de_json(data.get("type"), bot) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_message.py b/telegram/_message.py index f48418a8f8f..92b0b06f0da 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, TypedDict, Union from telegram._chat import Chat +from telegram._chatbackground import ChatBackground from telegram._chatboost import ChatBoostAdded from telegram._dice import Dice from telegram._files.animation import Animation @@ -553,6 +554,11 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 21.1 + chat_background_set (:obj:`telegram.ChatBackground`, optional): Service message: chat + background set. + + .. versionadded:: NEXT.VERSION + Attributes: message_id (:obj:`int`): Unique message identifier inside this chat. from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages @@ -853,6 +859,11 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 21.1 + chat_background_set (:obj:`telegram.ChatBackground`): Optional. Service message: chat + background set + + .. versionadded:: Next.Version + .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a :exc:`ValueError` when encountering a custom emoji. @@ -876,6 +887,7 @@ class Message(MaybeInaccessibleMessage): "caption", "caption_entities", "channel_chat_created", + "chat_background_set", "chat_shared", "connected_website", "contact", @@ -1029,6 +1041,7 @@ def __init__( business_connection_id: Optional[str] = None, sender_business_bot: Optional[User] = None, is_from_offline: Optional[bool] = None, + chat_background_set: Optional[ChatBackground] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -1127,6 +1140,7 @@ def __init__( self.business_connection_id: Optional[str] = business_connection_id self.sender_business_bot: Optional[User] = sender_business_bot self.is_from_offline: Optional[bool] = is_from_offline + self.chat_background_set: Optional[ChatBackground] = chat_background_set self._effective_attachment = DEFAULT_NONE @@ -1241,6 +1255,7 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Message"]: ) data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot) data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot) + data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot) # Unfortunately, this needs to be here due to cyclic imports from telegram._giveaway import ( # pylint: disable=import-outside-toplevel diff --git a/telegram/constants.py b/telegram/constants.py index e7ff6d03823..d3e3ed93c21 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -37,6 +37,8 @@ "SUPPORTED_WEBHOOK_PORTS", "ZERO_DATE", "AccentColor", + "BackgroundFill", + "BackgroundType", "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", @@ -1726,6 +1728,11 @@ class MessageType(StringEnum): .. versionadded:: 20.8 """ + CHAT_BACKGROUND_SET = "chat_background_set" + """:obj:`str`: Messages with :attr:`telegram.Message.chat_background_set`. + + .. versionadded:: NEXT.VERSION + """ CONNECTED_WEBSITE = "connected_website" """:obj:`str`: Messages with :attr:`telegram.Message.connected_website`.""" CONTACT = "contact" @@ -2878,3 +2885,39 @@ class ReactionEmoji(StringEnum): """:obj:`str`: Woman Shrugging""" POUTING_FACE = "😡" """:obj:`str`: Pouting face""" + + +class BackgroundType(StringEnum): + """This enum contains the available types of :class:`telegram.BackgroundType`. The enum + members of this enumeration are instances of :class:`str` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + FILL = "fill" + """:obj:`str`: A :class:`telegram.BackgroundType` with fill background.""" + WALLPAPER = "wallpaper" + """:obj:`str`: A :class:`telegram.BackgroundType` with wallpaper background.""" + PATTERN = "pattern" + """:obj:`str`: A :class:`telegram.BackgroundType` with pattern background.""" + CHAT_THEME = "chat_theme" + """:obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background.""" + + +class BackgroundFill(StringEnum): + """This enum contains the available types of :class:`telegram.BackgroundFill`. The enum + members of this enumeration are instances of :class:`str` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + SOLID = "solid" + """:obj:`str`: A :class:`telegram.BackgroundFill` with solid fill.""" + GRADIENT = "gradient" + """:obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill.""" + FREEFORM_GRADIENT = "freeform_gradient" + """:obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill.""" diff --git a/tests/test_chatbackground.py b/tests/test_chatbackground.py new file mode 100644 index 00000000000..f189896bc99 --- /dev/null +++ b/tests/test_chatbackground.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# 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/]. +import inspect +from copy import deepcopy +from typing import Union + +import pytest + +from telegram import ( + BackgroundFill, + BackgroundFillFreeformGradient, + BackgroundFillGradient, + BackgroundFillSolid, + BackgroundType, + BackgroundTypeChatTheme, + BackgroundTypeFill, + BackgroundTypePattern, + BackgroundTypeWallpaper, + Dice, + Document, +) +from tests.auxil.slots import mro_slots + +ignored = ["self", "api_kwargs"] + + +class BFDefaults: + color = 0 + top_color = 1 + bottom_color = 2 + rotation_angle = 45 + colors = [0, 1, 2] + + +def background_fill_solid(): + return BackgroundFillSolid(BFDefaults.color) + + +def background_fill_gradient(): + return BackgroundFillGradient( + BFDefaults.top_color, BFDefaults.bottom_color, BFDefaults.rotation_angle + ) + + +def background_fill_freeform_gradient(): + return BackgroundFillFreeformGradient(BFDefaults.colors) + + +class BTDefaults: + document = Document(1, 2) + fill = BackgroundFillSolid(color=0) + dark_theme_dimming = 20 + is_blurred = True + is_moving = False + intensity = 90 + is_inverted = False + theme_name = "ice" + + +def background_type_fill(): + return BackgroundTypeFill(BTDefaults.fill, BTDefaults.dark_theme_dimming) + + +def background_type_wallpaper(): + return BackgroundTypeWallpaper( + BTDefaults.document, + BTDefaults.dark_theme_dimming, + BTDefaults.is_blurred, + BTDefaults.is_moving, + ) + + +def background_type_pattern(): + return BackgroundTypePattern( + BTDefaults.document, + BTDefaults.fill, + BTDefaults.intensity, + BTDefaults.is_inverted, + BTDefaults.is_moving, + ) + + +def background_type_chat_theme(): + return BackgroundTypeChatTheme(BTDefaults.theme_name) + + +def make_json_dict( + instance: Union[BackgroundType, BackgroundFill], include_optional_args: bool = False +) -> dict: + """Used to make the json dict which we use for testing de_json. Similar to iter_args()""" + json_dict = {"type": instance.type} + sig = inspect.signature(instance.__class__.__init__) + + for param in sig.parameters.values(): + if param.name in ignored: # ignore irrelevant params + continue + + val = getattr(instance, param.name) + # Compulsory args- + if param.default is inspect.Parameter.empty: + if hasattr(val, "to_dict"): # convert the user object or any future ones to dict. + val = val.to_dict() + json_dict[param.name] = val + + # If we want to test all args (for de_json)- + elif param.default is not inspect.Parameter.empty and include_optional_args: + json_dict[param.name] = val + return json_dict + + +def iter_args( + instance: Union[BackgroundType, BackgroundFill], + de_json_inst: Union[BackgroundType, BackgroundFill], + include_optional: bool = False, +): + """ + We accept both the regular instance and de_json created instance and iterate over them for + easy one line testing later one. + """ + yield instance.type, de_json_inst.type # yield this here cause it's not available in sig. + + sig = inspect.signature(instance.__class__.__init__) + for param in sig.parameters.values(): + if param.name in ignored: + continue + inst_at, json_at = getattr(instance, param.name), getattr(de_json_inst, param.name) + if ( + param.default is not inspect.Parameter.empty and include_optional + ) or param.default is inspect.Parameter.empty: + yield inst_at, json_at + + +@pytest.fixture() +def background_type(request): + return request.param() + + +@pytest.mark.parametrize( + "background_type", + [ + background_type_fill, + background_type_wallpaper, + background_type_pattern, + background_type_chat_theme, + ], + indirect=True, +) +class TestBackgroundTypeWithoutRequest: + def test_slot_behaviour(self, background_type): + inst = background_type + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json_required_args(self, bot, background_type): + cls = background_type.__class__ + assert cls.de_json({}, bot) is None + + json_dict = make_json_dict(background_type) + const_background_type = BackgroundType.de_json(json_dict, bot) + assert const_background_type.api_kwargs == {} + + assert isinstance(const_background_type, BackgroundType) + assert isinstance(const_background_type, cls) + for bg_type_at, const_bg_type_at in iter_args(background_type, const_background_type): + assert bg_type_at == const_bg_type_at + + def test_de_json_all_args(self, bot, background_type): + json_dict = make_json_dict(background_type, include_optional_args=True) + const_background_type = BackgroundType.de_json(json_dict, bot) + + assert const_background_type.api_kwargs == {} + + assert isinstance(const_background_type, BackgroundType) + assert isinstance(const_background_type, background_type.__class__) + for bg_type_at, const_bg_type_at in iter_args( + background_type, const_background_type, True + ): + assert bg_type_at == const_bg_type_at + + def test_de_json_invalid_type(self, background_type, bot): + json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name} + background_type = BackgroundType.de_json(json_dict, bot) + + assert type(background_type) is BackgroundType + assert background_type.type == "invalid" + + def test_de_json_subclass(self, background_type, bot, chat_id): + """This makes sure that e.g. BackgroundTypeFill(data, bot) never returns a + BackgroundTypeWallpaper instance.""" + cls = background_type.__class__ + json_dict = make_json_dict(background_type, True) + assert type(cls.de_json(json_dict, bot)) is cls + + def test_to_dict(self, background_type): + bg_type_dict = background_type.to_dict() + + assert isinstance(bg_type_dict, dict) + assert bg_type_dict["type"] == background_type.type + + for slot in background_type.__slots__: # additional verification for the optional args + if slot in ("fill", "document"): + assert (getattr(background_type, slot)).to_dict() == bg_type_dict[slot] + continue + assert getattr(background_type, slot) == bg_type_dict[slot] + + def test_equality(self, background_type): + a = BackgroundType(type="type") + b = BackgroundType(type="type") + c = background_type + d = deepcopy(background_type) + e = Dice(4, "emoji") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + assert c == d + assert hash(c) == hash(d) + + assert c != e + assert hash(c) != hash(e) + + +@pytest.fixture() +def background_fill(request): + return request.param() + + +@pytest.mark.parametrize( + "background_fill", + [ + background_fill_solid, + background_fill_gradient, + background_fill_freeform_gradient, + ], + indirect=True, +) +class TestBackgroundFillWithoutRequest: + def test_slot_behaviour(self, background_fill): + inst = background_fill + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json_required_args(self, bot, background_fill): + cls = background_fill.__class__ + assert cls.de_json({}, bot) is None + + json_dict = make_json_dict(background_fill) + const_background_fill = BackgroundFill.de_json(json_dict, bot) + assert const_background_fill.api_kwargs == {} + + assert isinstance(const_background_fill, BackgroundFill) + assert isinstance(const_background_fill, cls) + for bg_fill_at, const_bg_fill_at in iter_args(background_fill, const_background_fill): + assert bg_fill_at == const_bg_fill_at + + def test_de_json_all_args(self, bot, background_fill): + json_dict = make_json_dict(background_fill, include_optional_args=True) + const_background_fill = BackgroundFill.de_json(json_dict, bot) + + assert const_background_fill.api_kwargs == {} + + assert isinstance(const_background_fill, BackgroundFill) + assert isinstance(const_background_fill, background_fill.__class__) + for bg_fill_at, const_bg_fill_at in iter_args( + background_fill, const_background_fill, True + ): + assert bg_fill_at == const_bg_fill_at + + def test_de_json_invalid_type(self, background_fill, bot): + json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name} + background_fill = BackgroundFill.de_json(json_dict, bot) + + assert type(background_fill) is BackgroundFill + assert background_fill.type == "invalid" + + def test_de_json_subclass(self, background_fill, bot): + """This makes sure that e.g. BackgroundFillSolid(data, bot) never returns a + BackgroundFillGradient instance.""" + cls = background_fill.__class__ + json_dict = make_json_dict(background_fill, True) + assert type(cls.de_json(json_dict, bot)) is cls + + def test_to_dict(self, background_fill): + bg_fill_dict = background_fill.to_dict() + + assert isinstance(bg_fill_dict, dict) + assert bg_fill_dict["type"] == background_fill.type + + for slot in background_fill.__slots__: # additional verification for the optional args + if slot == "colors": + assert getattr(background_fill, slot) == tuple(bg_fill_dict[slot]) + continue + assert getattr(background_fill, slot) == bg_fill_dict[slot] + + def test_equality(self, background_fill): + a = BackgroundFill(type="type") + b = BackgroundFill(type="type") + c = background_fill + d = deepcopy(background_fill) + e = Dice(4, "emoji") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + assert c == d + assert hash(c) == hash(d) + + assert c != e + assert hash(c) != hash(e) diff --git a/tests/test_message.py b/tests/test_message.py index 440ec50651c..46a7f89b865 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -24,8 +24,10 @@ from telegram import ( Animation, Audio, + BackgroundTypeChatTheme, Bot, Chat, + ChatBackground, ChatBoostAdded, ChatShared, Contact, @@ -270,6 +272,7 @@ def message(bot): {"is_from_offline": True}, {"sender_business_bot": User(1, "BusinessBot", True)}, {"business_connection_id": "123456789"}, + {"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))}, ], ids=[ "reply", @@ -338,6 +341,7 @@ def message(bot): "sender_business_bot", "business_connection_id", "is_from_offline", + "chat_background_set", ], ) def message_params(bot, request): From f512c0f9b3866cd0e67889612835e18ad0777379 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Fri, 10 May 2024 03:46:55 +0300 Subject: [PATCH 04/12] Fix test_enum_types (exclude `Chatbackground.type`) --- tests/test_enum_types.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_enum_types.py b/tests/test_enum_types.py index 9e7140ee1df..b16002c6642 100644 --- a/tests/test_enum_types.py +++ b/tests/test_enum_types.py @@ -27,7 +27,10 @@ / "_passport", } -exclude_patterns = {re.compile(re.escape("self.type: ReactionType = type"))} +exclude_patterns = { + re.compile(re.escape("self.type: ReactionType = type")), + re.compile(re.escape("self.type: BackgroundType = type")), +} def test_types_are_converted_to_enum(): From fbac296843ef0b0cb2ae233d0d76c89a67e7a60d Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Fri, 10 May 2024 06:17:32 +0300 Subject: [PATCH 05/12] Add tg-const values and improve docstrings formatting --- docs/source/telegram.backgroundfill.rst | 2 +- telegram/_bot.py | 3 +- telegram/_chatbackground.py | 96 ++++++++++++++----------- telegram/constants.py | 74 +++++++++++++++++++ 4 files changed, 133 insertions(+), 42 deletions(-) diff --git a/docs/source/telegram.backgroundfill.rst b/docs/source/telegram.backgroundfill.rst index 5f7e68f9f08..df9310e7ab2 100644 --- a/docs/source/telegram.backgroundfill.rst +++ b/docs/source/telegram.backgroundfill.rst @@ -1,5 +1,5 @@ BackgroundFill -======================= +============== .. versionadded:: NEXT.VERSION diff --git a/telegram/_bot.py b/telegram/_bot.py index 937d186ff01..2708fd2e439 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -2760,7 +2760,8 @@ async def edit_message_live_location( reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new inline keyboard. live_period (:obj:`int`, optional): New period in seconds during which the location - can be updated, starting from the message send date. If `0x7FFFFFFF` is specified, + can be updated, starting from the message send date. If + :tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` is specified, then the location can be updated forever. Otherwise, the new value must not exceed the current live_period by more than a day, and the live location expiration date must remain within the next 90 days. If not specified, then `live_period` diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py index 4630d4b8477..deab8b2cfa7 100644 --- a/telegram/_chatbackground.py +++ b/telegram/_chatbackground.py @@ -17,7 +17,7 @@ # 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 chat backgrounds.""" -from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Type +from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Tuple, Type from telegram import constants from telegram._files.document import Document @@ -44,14 +44,13 @@ class BackgroundFill(TelegramObject): Args: type (:obj:`str`): Type of the background fill. Can be one of: - :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` - or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. + :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` + or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. Attributes: type (:obj:`str`): Type of the background fill. Can be one of: - :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` - or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. - + :attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT` + or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. """ __slots__ = ("type",) @@ -135,7 +134,10 @@ class BackgroundFillGradient(BackgroundFill): top_color (:obj:`int`): Top color of the gradient in the `RGB24` format. bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. rotation_angle (:obj:`int`): Clockwise rotation angle of the background - fill in degrees; `0-359`. + fill in degrees; + :tg-const:`telegram.constants.BackgroundFillLimit.MIN_ROTATION_ANGLE`- + :tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. + Attributes: type (:obj:`str`): Type of the background fill. Always @@ -143,7 +145,9 @@ class BackgroundFillGradient(BackgroundFill): top_color (:obj:`int`): Top color of the gradient in the `RGB24` format. bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. rotation_angle (:obj:`int`): Clockwise rotation angle of the background - fill in degrees; `0-359`. + fill in degrees; + :tg-const:`telegram.constants.BackgroundFillLimit.MIN_ROTATION_ANGLE`- + :tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. """ __slots__ = ("bottom_color", "rotation_angle", "top_color") @@ -172,13 +176,13 @@ class BackgroundFillFreeformGradient(BackgroundFill): Args: colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to - generate the freeform gradient in the `RGB24` format + generate the freeform gradient in the `RGB24` format Attributes: type (:obj:`str`): Type of the background fill. Always - :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. + :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`. colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to - generate the freeform gradient in the `RGB24` format + generate the freeform gradient in the `RGB24` format """ __slots__ = ("colors",) @@ -192,7 +196,7 @@ def __init__( super().__init__(type=self.FREEFORM_GRADIENT, api_kwargs=api_kwargs) with self._unfrozen(): - self.colors: Sequence[int] = parse_sequence_arg(colors) + self.colors: Tuple[int] = parse_sequence_arg(colors) class BackgroundType(TelegramObject): @@ -210,15 +214,15 @@ class BackgroundType(TelegramObject): Args: type (:obj:`str`): Type of the background. Can be one of: - :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` - :attr:`~telegram.BackgroundType.PATTERN` or - :attr:`~telegram.BackgroundType.CHAT_THEME`. + :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` + :attr:`~telegram.BackgroundType.PATTERN` or + :attr:`~telegram.BackgroundType.CHAT_THEME`. Attributes: type (:obj:`str`): Type of the background. Can be one of: - :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` - :attr:`~telegram.BackgroundType.PATTERN` or - :attr:`~telegram.BackgroundType.CHAT_THEME`. + :attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER` + :attr:`~telegram.BackgroundType.PATTERN` or + :attr:`~telegram.BackgroundType.CHAT_THEME`. """ @@ -282,14 +286,18 @@ class BackgroundTypeFill(BackgroundType): Args: fill (:obj:`telegram.BackgroundFill`): The background fill. dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a - percentage; `0-100`. + percentage; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. Attributes: type (:obj:`str`): Type of the background. Always - :attr:`~telegram.BackgroundType.FILL`. + :attr:`~telegram.BackgroundType.FILL`. fill (:obj:`telegram.BackgroundFill`): The background fill. dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a - percentage; `0-100`. + percentage; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. """ __slots__ = ("dark_theme_dimming", "fill") @@ -317,22 +325,26 @@ class BackgroundTypeWallpaper(BackgroundType): Args: document (:obj:`telegram.Document`): Document with the wallpaper dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a - percentage; `0-100` + percentage; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. is_blurred (:obj:`bool`, optional): :obj:`True`, if the wallpaper is downscaled to fit - in a `450x450` square and then box-blurred with radius `12` + in a 450x450 square and then box-blurred with radius 12 is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly - when the device is tilted + when the device is tilted Attributes: type (:obj:`str`): Type of the background. Always - :attr:`~telegram.BackgroundType.WALLPAPER`. + :attr:`~telegram.BackgroundType.WALLPAPER`. document (:obj:`telegram.Document`): Document with the wallpaper dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a - percentage; `0-100` + percentage; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. is_blurred (:obj:`bool`): Optional. :obj:`True`, if the wallpaper is downscaled to fit - in a `450x450` square and then box-blurred with radius `12` + in a 450x450 square and then box-blurred with radius 12 is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly - when the device is tilted + when the device is tilted """ __slots__ = ("dark_theme_dimming", "document", "is_blurred", "is_moving") @@ -368,28 +380,32 @@ class BackgroundTypePattern(BackgroundType): Args: document (:obj:`telegram.Document`): Document with the pattern. fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with - the pattern. + the pattern. intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled - background; `0-100`. + background; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_INTENSITY`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. is_inverted (:obj:`int`, optional): :obj:`True`, if the background fill must be applied - only to the pattern itself. All other pixels are black in this case. For dark - themes only. + only to the pattern itself. All other pixels are black in this case. For dark + themes only. is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly - when the device is tilted. + when the device is tilted. Attributes: type (:obj:`str`): Type of the background. Always - :attr:`~telegram.BackgroundType.PATTERN`. + :attr:`~telegram.BackgroundType.PATTERN`. document (:obj:`telegram.Document`): Document with the pattern. fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with - the pattern. + the pattern. intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled - background; `0-100`. + background; + :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_INTENSITY`- + :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. is_inverted (:obj:`int`): Optional. :obj:`True`, if the background fill must be applied - only to the pattern itself. All other pixels are black in this case. For dark - themes only. + only to the pattern itself. All other pixels are black in this case. For dark + themes only. is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly - when the device is tilted. + when the device is tilted. """ __slots__ = ( @@ -433,7 +449,7 @@ class BackgroundTypeChatTheme(BackgroundType): Attributes: type (:obj:`str`): Type of the background. Always - :attr:`~telegram.BackgroundType.CHAT_THEME`. + :attr:`~telegram.BackgroundType.CHAT_THEME`. theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji. """ diff --git a/telegram/constants.py b/telegram/constants.py index d3e3ed93c21..37b9b65e896 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -38,7 +38,9 @@ "ZERO_DATE", "AccentColor", "BackgroundFill", + "BackgroundFillLimit", "BackgroundType", + "BackgroundTypeLimit", "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", @@ -824,6 +826,63 @@ class ChatLimit(IntEnum): """ +class BackgroundTypeLimit(IntEnum): + """This enum contains limitations for :class:`telegram.BackgroundTypeFill`, + :class:`telegram.BackgroundTypeWallpaper` and :class:`telegram.BackgroundTypePattern`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MIN_DIMMING = 0 + """:obj:`int`: Minimum value allowed for: + + * :paramref:`~telegram.BackgroundTypeFill.dark_theme_dimming` parameter of + :class:`telegram.BackgroundTypeFill` + * :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of + :class:`telegram.BackgroundTypeWallpaper` + """ + MAX_DIMMING = 100 + """:obj:`int`: Maximum value allowed for: + + * :paramref:`~telegram.BackgroundTypeFill.dark_theme_dimming` parameter of + :class:`telegram.BackgroundTypeFill` + * :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of + :class:`telegram.BackgroundTypeWallpaper` + """ + MIN_INTENSITY = 0 + """:obj:`int`: Minimum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity` + parameter of :class:`telegram.BackgroundTypePattern` + """ + MAX_INTENSITY = 100 + """:obj:`int`: Maximum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity` + parameter of :class:`telegram.BackgroundTypePattern` + """ + + +class BackgroundFillLimit(IntEnum): + """This enum contains limitations for :class:`telegram.BackgroundFillGradient`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MIN_ROTATION_ANGLE = 0 + """:obj:`int`: Minimum value allowed for + :paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of + :class:`telegram.BackgroundFillGradient` + """ + MAX_ROTATION_ANGLE = 359 + """:obj:`int`: Maximum value allowed for: + :paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of + :class:`telegram.BackgroundFillGradient` + """ + + class ChatMemberStatus(StringEnum): """This enum contains the available states for :class:`telegram.ChatMember`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. @@ -1429,6 +1488,21 @@ class LocationLimit(IntEnum): :meth:`telegram.Bot.send_location` """ + LIVE_PERIOD_FOREVER = int(hex(0x7FFFFFFF), 16) + """:obj:`int`: Value for live locations that can be edited indefinitely. Passed in: + + * :paramref:`~telegram.InlineQueryResultLocation.live_period` parameter of + :class:`telegram.InlineQueryResultLocation` + * :paramref:`~telegram.InputLocationMessageContent.live_period` parameter of + :class:`telegram.InputLocationMessageContent` + * :paramref:`~telegram.Bot.edit_message_live_location.live_period` parameter of + :meth:`telegram.Bot.edit_message_live_location` + * :paramref:`~telegram.Bot.send_location.live_period` parameter of + :meth:`telegram.Bot.send_location` + + .. versionadded:: NEXT.VERSION + """ + MIN_PROXIMITY_ALERT_RADIUS = 1 """:obj:`int`: Minimum value allowed for: From 411386338ef9504f949069dc5fe900b1cdea3e96 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Fri, 10 May 2024 06:24:16 +0300 Subject: [PATCH 06/12] Rename enums of classes `Background{Type, Fill}`. --- telegram/_chatbackground.py | 34 ++++++++++++++++++---------------- telegram/constants.py | 8 ++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py index deab8b2cfa7..91bf58ae9db 100644 --- a/telegram/_chatbackground.py +++ b/telegram/_chatbackground.py @@ -55,12 +55,14 @@ class BackgroundFill(TelegramObject): __slots__ = ("type",) - SOLID: Final[constants.BackgroundFill] = constants.BackgroundFill.SOLID - """:const:`telegram.constants.BackgroundFill.SOLID`""" - GRADIENT: Final[constants.BackgroundFill] = constants.BackgroundFill.GRADIENT - """:const:`telegram.constants.BackgroundFill.GRADIENT`""" - FREEFORM_GRADIENT: Final[constants.BackgroundFill] = constants.BackgroundFill.FREEFORM_GRADIENT - """:const:`telegram.constants.BackgroundFill.FREEFORM_GRADIENT`""" + SOLID: Final[constants.BackgroundFillType] = constants.BackgroundFillType.SOLID + """:const:`telegram.constants.BackgroundFillType.SOLID`""" + GRADIENT: Final[constants.BackgroundFillType] = constants.BackgroundFillType.GRADIENT + """:const:`telegram.constants.BackgroundFillType.GRADIENT`""" + FREEFORM_GRADIENT: Final[constants.BackgroundFillType] = ( + constants.BackgroundFillType.FREEFORM_GRADIENT + ) + """:const:`telegram.constants.BackgroundFillType.FREEFORM_GRADIENT`""" def __init__( self, @@ -70,7 +72,7 @@ def __init__( ): super().__init__(api_kwargs=api_kwargs) # Required by all subclasses - self.type: str = enum.get_member(constants.BackgroundFill, type, type) + self.type: str = enum.get_member(constants.BackgroundFillType, type, type) self._id_attrs = (self.type,) self._freeze() @@ -228,14 +230,14 @@ class BackgroundType(TelegramObject): __slots__ = ("type",) - FILL: Final[constants.BackgroundType] = constants.BackgroundType.FILL - """:const:`telegram.constants.BackgroundType.FILL`""" - WALLPAPER: Final[constants.BackgroundType] = constants.BackgroundType.WALLPAPER - """:const:`telegram.constants.BackgroundType.WALLPAPER`""" - PATTERN: Final[constants.BackgroundType] = constants.BackgroundType.PATTERN - """:const:`telegram.constants.BackgroundType.PATTERN`""" - CHAT_THEME: Final[constants.BackgroundType] = constants.BackgroundType.CHAT_THEME - """:const:`telegram.constants.BackgroundType.CHAT_THEME`""" + FILL: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.FILL + """:const:`telegram.constants.BackgroundTypeType.FILL`""" + WALLPAPER: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.WALLPAPER + """:const:`telegram.constants.BackgroundTypeType.WALLPAPER`""" + PATTERN: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.PATTERN + """:const:`telegram.constants.BackgroundTypeType.PATTERN`""" + CHAT_THEME: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.CHAT_THEME + """:const:`telegram.constants.BackgroundTypeType.CHAT_THEME`""" def __init__( self, @@ -245,7 +247,7 @@ def __init__( ): super().__init__(api_kwargs=api_kwargs) # Required by all subclasses - self.type: str = enum.get_member(constants.BackgroundType, type, type) + self.type: str = enum.get_member(constants.BackgroundTypeType, type, type) self._id_attrs = (self.type,) self._freeze() diff --git a/telegram/constants.py b/telegram/constants.py index 37b9b65e896..19cde7002ec 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -37,9 +37,9 @@ "SUPPORTED_WEBHOOK_PORTS", "ZERO_DATE", "AccentColor", - "BackgroundFill", + "BackgroundFillType", "BackgroundFillLimit", - "BackgroundType", + "BackgroundTypeType", "BackgroundTypeLimit", "BotCommandLimit", "BotCommandScopeType", @@ -2961,7 +2961,7 @@ class ReactionEmoji(StringEnum): """:obj:`str`: Pouting face""" -class BackgroundType(StringEnum): +class BackgroundTypeType(StringEnum): """This enum contains the available types of :class:`telegram.BackgroundType`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. @@ -2980,7 +2980,7 @@ class BackgroundType(StringEnum): """:obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background.""" -class BackgroundFill(StringEnum): +class BackgroundFillType(StringEnum): """This enum contains the available types of :class:`telegram.BackgroundFill`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. From 750f02df1cc81ae280f9001efe81634f0e9ab505 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Fri, 10 May 2024 07:19:42 +0300 Subject: [PATCH 07/12] Extend _id_attrs to `Background*` subclasses as well. --- telegram/_chatbackground.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_chatbackground.py | 16 ++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py index 91bf58ae9db..b1d3c74608d 100644 --- a/telegram/_chatbackground.py +++ b/telegram/_chatbackground.py @@ -103,6 +103,9 @@ class BackgroundFillSolid(BackgroundFill): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`color` is equal. + Args: color (:obj:`int`): The color of the background fill in the `RGB24` format. @@ -125,6 +128,8 @@ def __init__( with self._unfrozen(): self.color: int = color + self._id_attrs = (self.color,) + class BackgroundFillGradient(BackgroundFill): """ @@ -132,6 +137,10 @@ class BackgroundFillGradient(BackgroundFill): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`top_color`, :attr:`bottom_color` + and :attr:`rotation_angle` are equal. + Args: top_color (:obj:`int`): Top color of the gradient in the `RGB24` format. bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. @@ -169,6 +178,8 @@ def __init__( self.bottom_color: int = bottom_color self.rotation_angle: int = rotation_angle + self._id_attrs = (self.top_color, self.bottom_color, self.rotation_angle) + class BackgroundFillFreeformGradient(BackgroundFill): """ @@ -176,6 +187,9 @@ class BackgroundFillFreeformGradient(BackgroundFill): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`colors` is equal. + Args: colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to generate the freeform gradient in the `RGB24` format @@ -200,6 +214,8 @@ def __init__( with self._unfrozen(): self.colors: Tuple[int] = parse_sequence_arg(colors) + self._id_attrs = (self.colors,) + class BackgroundType(TelegramObject): """Base class for Telegram BackgroundType Objects. It can be one of: @@ -285,6 +301,9 @@ class BackgroundTypeFill(BackgroundType): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`fill` and :attr:`dark_theme_dimming` are equal. + Args: fill (:obj:`telegram.BackgroundFill`): The background fill. dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a @@ -317,6 +336,8 @@ def __init__( self.fill: BackgroundFill = fill self.dark_theme_dimming: int = dark_theme_dimming + self._id_attrs = (self.fill, self.dark_theme_dimming) + class BackgroundTypeWallpaper(BackgroundType): """ @@ -324,6 +345,9 @@ class BackgroundTypeWallpaper(BackgroundType): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`document` and :attr:`dark_theme_dimming` are equal. + Args: document (:obj:`telegram.Document`): Document with the wallpaper dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a @@ -370,6 +394,8 @@ def __init__( self.is_blurred: Optional[bool] = is_blurred self.is_moving: Optional[bool] = is_moving + self._id_attrs = (self.document, self.dark_theme_dimming) + class BackgroundTypePattern(BackgroundType): """ @@ -379,6 +405,9 @@ class BackgroundTypePattern(BackgroundType): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`document` and :attr:`fill` and :attr:`intensity` are equal. + Args: document (:obj:`telegram.Document`): Document with the pattern. fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with @@ -439,6 +468,8 @@ def __init__( self.is_inverted: Optional[bool] = is_inverted self.is_moving: Optional[bool] = is_moving + self._id_attrs = (self.document, self.fill, self.intensity) + class BackgroundTypeChatTheme(BackgroundType): """ @@ -446,6 +477,9 @@ class BackgroundTypeChatTheme(BackgroundType): .. versionadded:: NEXT.VERSION + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`theme_name` is equal. + Args: theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji. @@ -468,6 +502,8 @@ def __init__( with self._unfrozen(): self.theme_name: str = theme_name + self._id_attrs = (self.theme_name,) + class ChatBackground(TelegramObject): """ diff --git a/tests/test_chatbackground.py b/tests/test_chatbackground.py index f189896bc99..1f8be1eb451 100644 --- a/tests/test_chatbackground.py +++ b/tests/test_chatbackground.py @@ -226,6 +226,11 @@ def test_equality(self, background_type): c = background_type d = deepcopy(background_type) e = Dice(4, "emoji") + sig = inspect.signature(background_type.__class__.__init__) + params = [ + "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"] + ] + f = background_type.__class__(*params) assert a == b assert hash(a) == hash(b) @@ -245,6 +250,9 @@ def test_equality(self, background_type): assert c != e assert hash(c) != hash(e) + assert f != c + assert hash(f) != hash(c) + @pytest.fixture() def background_fill(request): @@ -325,6 +333,11 @@ def test_equality(self, background_fill): c = background_fill d = deepcopy(background_fill) e = Dice(4, "emoji") + sig = inspect.signature(background_fill.__class__.__init__) + params = [ + "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"] + ] + f = background_fill.__class__(*params) assert a == b assert hash(a) == hash(b) @@ -343,3 +356,6 @@ def test_equality(self, background_fill): assert c != e assert hash(c) != hash(e) + + assert f != c + assert hash(f) != hash(c) From f82112bd810252e9ca327230debeda7cee2f5664 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Fri, 10 May 2024 07:51:06 +0300 Subject: [PATCH 08/12] Add `StatusUpdate.CHAT_BACKGROUND_SET` filter & tetsts. --- telegram/ext/filters.py | 12 +++++++++++- tests/ext/test_filters.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index f2820d2b25a..72145edb378 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -1909,7 +1909,8 @@ class _All(UpdateFilter): def filter(self, update: Update) -> bool: return bool( # keep this alphabetically sorted for easier maintenance - StatusUpdate.CHAT_CREATED.check_update(update) + StatusUpdate.CHAT_BACKGROUND_SET.check_update(update) + or StatusUpdate.CHAT_CREATED.check_update(update) or StatusUpdate.CHAT_SHARED.check_update(update) or StatusUpdate.CONNECTED_WEBSITE.check_update(update) or StatusUpdate.DELETE_CHAT_PHOTO.check_update(update) @@ -1942,6 +1943,15 @@ def filter(self, update: Update) -> bool: ALL = _All(name="filters.StatusUpdate.ALL") """Messages that contain any of the below.""" + class _ChatBackgroundSet(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.chat_background_set) + + CHAT_BACKGROUND_SET = _ChatBackgroundSet(name="filters.StatusUpdate.CHAT_BACKGROUND_SET") + """Messages that contain :attr:`telegram.Message.chat_background_set`.""" + class _ChatCreated(MessageFilter): __slots__ = () diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index 694ea009a6f..fc88e428404 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -1090,6 +1090,11 @@ def test_filters_status_update(self, update): assert filters.StatusUpdate.GIVEAWAY_COMPLETED.check_update(update) update.message.giveaway_completed = None + update.message.chat_background_set = "test_background" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.CHAT_BACKGROUND_SET.check_update(update) + update.message.chat_background_set = None + def test_filters_forwarded(self, update, message_origin_user): assert filters.FORWARDED.check_update(update) update.message.forward_origin = MessageOriginHiddenUser(datetime.datetime.utcnow(), 1) From 78d3ec734fe07dbf8fdecd55e530872417f554f4 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Sat, 11 May 2024 03:18:10 +0300 Subject: [PATCH 09/12] Remove zero-valued `tg-const`s. --- telegram/_chatbackground.py | 24 ++++++++---------------- telegram/constants.py | 17 ----------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py index b1d3c74608d..779e2875d8d 100644 --- a/telegram/_chatbackground.py +++ b/telegram/_chatbackground.py @@ -146,8 +146,7 @@ class BackgroundFillGradient(BackgroundFill): bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. rotation_angle (:obj:`int`): Clockwise rotation angle of the background fill in degrees; - :tg-const:`telegram.constants.BackgroundFillLimit.MIN_ROTATION_ANGLE`- - :tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. + 0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. Attributes: @@ -157,8 +156,7 @@ class BackgroundFillGradient(BackgroundFill): bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format. rotation_angle (:obj:`int`): Clockwise rotation angle of the background fill in degrees; - :tg-const:`telegram.constants.BackgroundFillLimit.MIN_ROTATION_ANGLE`- - :tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. + 0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. """ __slots__ = ("bottom_color", "rotation_angle", "top_color") @@ -308,8 +306,7 @@ class BackgroundTypeFill(BackgroundType): fill (:obj:`telegram.BackgroundFill`): The background fill. dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a percentage; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. Attributes: type (:obj:`str`): Type of the background. Always @@ -317,8 +314,7 @@ class BackgroundTypeFill(BackgroundType): fill (:obj:`telegram.BackgroundFill`): The background fill. dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a percentage; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. """ __slots__ = ("dark_theme_dimming", "fill") @@ -352,8 +348,7 @@ class BackgroundTypeWallpaper(BackgroundType): document (:obj:`telegram.Document`): Document with the wallpaper dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a percentage; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. is_blurred (:obj:`bool`, optional): :obj:`True`, if the wallpaper is downscaled to fit in a 450x450 square and then box-blurred with radius 12 is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly @@ -365,8 +360,7 @@ class BackgroundTypeWallpaper(BackgroundType): document (:obj:`telegram.Document`): Document with the wallpaper dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a percentage; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_DIMMING`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. is_blurred (:obj:`bool`): Optional. :obj:`True`, if the wallpaper is downscaled to fit in a 450x450 square and then box-blurred with radius 12 is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly @@ -414,8 +408,7 @@ class BackgroundTypePattern(BackgroundType): the pattern. intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled background; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_INTENSITY`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. is_inverted (:obj:`int`, optional): :obj:`True`, if the background fill must be applied only to the pattern itself. All other pixels are black in this case. For dark themes only. @@ -430,8 +423,7 @@ class BackgroundTypePattern(BackgroundType): the pattern. intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled background; - :tg-const:`telegram.constants.BackgroundTypeLimit.MIN_INTENSITY`- - :tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`. is_inverted (:obj:`int`): Optional. :obj:`True`, if the background fill must be applied only to the pattern itself. All other pixels are black in this case. For dark themes only. diff --git a/telegram/constants.py b/telegram/constants.py index 19cde7002ec..26ea10fe15f 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -836,14 +836,6 @@ class BackgroundTypeLimit(IntEnum): __slots__ = () - MIN_DIMMING = 0 - """:obj:`int`: Minimum value allowed for: - - * :paramref:`~telegram.BackgroundTypeFill.dark_theme_dimming` parameter of - :class:`telegram.BackgroundTypeFill` - * :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of - :class:`telegram.BackgroundTypeWallpaper` - """ MAX_DIMMING = 100 """:obj:`int`: Maximum value allowed for: @@ -852,10 +844,6 @@ class BackgroundTypeLimit(IntEnum): * :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of :class:`telegram.BackgroundTypeWallpaper` """ - MIN_INTENSITY = 0 - """:obj:`int`: Minimum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity` - parameter of :class:`telegram.BackgroundTypePattern` - """ MAX_INTENSITY = 100 """:obj:`int`: Maximum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity` parameter of :class:`telegram.BackgroundTypePattern` @@ -871,11 +859,6 @@ class BackgroundFillLimit(IntEnum): __slots__ = () - MIN_ROTATION_ANGLE = 0 - """:obj:`int`: Minimum value allowed for - :paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of - :class:`telegram.BackgroundFillGradient` - """ MAX_ROTATION_ANGLE = 359 """:obj:`int`: Maximum value allowed for: :paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of From bad6ba14f655412625e1dc8ca16e4f27561c4d4a Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Sat, 11 May 2024 03:30:41 +0300 Subject: [PATCH 10/12] Fix pre-commit issues. * sort constants.__all__ * properly annotate `self.colors = parse_sequence_arg(..)` --- telegram/_chatbackground.py | 2 +- telegram/constants.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/telegram/_chatbackground.py b/telegram/_chatbackground.py index 779e2875d8d..2724613af75 100644 --- a/telegram/_chatbackground.py +++ b/telegram/_chatbackground.py @@ -210,7 +210,7 @@ def __init__( super().__init__(type=self.FREEFORM_GRADIENT, api_kwargs=api_kwargs) with self._unfrozen(): - self.colors: Tuple[int] = parse_sequence_arg(colors) + self.colors: Tuple[int, ...] = parse_sequence_arg(colors) self._id_attrs = (self.colors,) diff --git a/telegram/constants.py b/telegram/constants.py index 26ea10fe15f..a12bb08dee3 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -37,10 +37,10 @@ "SUPPORTED_WEBHOOK_PORTS", "ZERO_DATE", "AccentColor", - "BackgroundFillType", "BackgroundFillLimit", - "BackgroundTypeType", + "BackgroundFillType", "BackgroundTypeLimit", + "BackgroundTypeType", "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", From cb9e17e791ca2714b904c48c3bec629c551c1e93 Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Sat, 11 May 2024 03:58:47 +0300 Subject: [PATCH 11/12] Adapt test_offical adding attrs to PTB_IGNORED_PARAMS. --- tests/test_official/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index 89892741bd4..d812c7f56ed 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -143,6 +143,8 @@ def ptb_extra_params(object_name: str) -> set[str]: r"MessageOrigin\w+": {"type"}, r"ChatBoostSource\w+": {"source"}, r"ReactionType\w+": {"type"}, + r"BackgroundType\w+": {"type"}, + r"BackgroundFill\w+": {"type"}, } From 7956323adeee9390ad6a04ebf62d6175515f6bdd Mon Sep 17 00:00:00 2001 From: aelkheir <90580077+aelkheir@users.noreply.github.com> Date: Sat, 11 May 2024 04:12:45 +0300 Subject: [PATCH 12/12] Add same attrs to PTB_EXTRA_PARAMS. please pass V --- tests/test_official/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index d812c7f56ed..128b30089fb 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -120,6 +120,8 @@ class ParamTypeCheckingExceptions: "ChatBoostSource": {"source"}, # attributes common to all subclasses "MessageOrigin": {"type", "date"}, # attributes common to all subclasses "ReactionType": {"type"}, # attributes common to all subclasses + "BackgroundType": {"type"}, # attributes common to all subclasses + "BackgroundFill": {"type"}, # attributes common to all subclasses "InputTextMessageContent": {"disable_web_page_preview"}, # convenience arg, here for bw compat }