diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 5e8907abf08..0e3c92e3893 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..df9310e7ab2 --- /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 dad01ee38ef..a0e7535b1c8 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", @@ -259,6 +269,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/_bot.py b/telegram/_bot.py index d3cf64402b0..71a88763593 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,15 @@ 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 + :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` + remains unchanged + + .. versionadded:: NEXT.VERSION. Keyword Args: location (:class:`telegram.Location`, optional): The location to send. @@ -2790,6 +2800,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/_chatbackground.py b/telegram/_chatbackground.py new file mode 100644 index 00000000000..2724613af75 --- /dev/null +++ b/telegram/_chatbackground.py @@ -0,0 +1,540 @@ +#!/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, Tuple, 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.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, + 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.BackgroundFillType, 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 + + 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. + + 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 + + self._id_attrs = (self.color,) + + +class BackgroundFillGradient(BackgroundFill): + """ + The background is a gradient fill. + + .. 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. + rotation_angle (:obj:`int`): Clockwise rotation angle of the background + fill in degrees; + 0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. + + + 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-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`. + """ + + __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 + + self._id_attrs = (self.top_color, self.bottom_color, self.rotation_angle) + + +class BackgroundFillFreeformGradient(BackgroundFill): + """ + The background is a freeform gradient that rotates after every message in the chat. + + .. 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 + + 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: 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: + + * :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.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, + 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.BackgroundTypeType, 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 + + 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 + percentage; + 0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + + 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-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`. + """ + + __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 + + self._id_attrs = (self.fill, self.dark_theme_dimming) + + +class BackgroundTypeWallpaper(BackgroundType): + """ + The background is a wallpaper in the `JPEG` format. + + .. 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 + percentage; + 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 + 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-: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 + 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 + + self._id_attrs = (self.document, self.dark_theme_dimming) + + +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 + + 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 + the pattern. + intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled + background; + 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. + 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-: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. + 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 + + self._id_attrs = (self.document, self.fill, self.intensity) + + +class BackgroundTypeChatTheme(BackgroundType): + """ + The background is taken directly from a built-in chat theme. + + .. 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. + + 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 + + self._id_attrs = (self.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/_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/telegram/_message.py b/telegram/_message.py index 76d8fc270d9..3988dd46252 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 @@ -554,6 +555,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 @@ -854,6 +860,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. @@ -877,6 +888,7 @@ class Message(MaybeInaccessibleMessage): "caption", "caption_entities", "channel_chat_created", + "chat_background_set", "chat_shared", "connected_website", "contact", @@ -1030,6 +1042,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, ): @@ -1128,6 +1141,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 @@ -1242,6 +1256,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 @@ -3654,6 +3669,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, @@ -3695,6 +3711,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/constants.py b/telegram/constants.py index e7ff6d03823..a12bb08dee3 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -37,6 +37,10 @@ "SUPPORTED_WEBHOOK_PORTS", "ZERO_DATE", "AccentColor", + "BackgroundFillLimit", + "BackgroundFillType", + "BackgroundTypeLimit", + "BackgroundTypeType", "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", @@ -822,6 +826,46 @@ 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__ = () + + 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` + """ + 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__ = () + + 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. @@ -1427,6 +1471,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: @@ -1726,6 +1785,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 +2942,39 @@ class ReactionEmoji(StringEnum): """:obj:`str`: Woman Shrugging""" POUTING_FACE = "😡" """:obj:`str`: Pouting face""" + + +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. + + .. 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 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. + + .. 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/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 9e932dd3cd8..4c2075d4a82 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -1538,6 +1538,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, @@ -1557,6 +1558,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/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/_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/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) 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_chatbackground.py b/tests/test_chatbackground.py new file mode 100644 index 00000000000..1f8be1eb451 --- /dev/null +++ b/tests/test_chatbackground.py @@ -0,0 +1,361 @@ +#!/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") + 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) + + 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) + + assert f != c + assert hash(f) != hash(c) + + +@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") + 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) + + 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) + + assert f != c + assert hash(f) != hash(c) 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( 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(): diff --git a/tests/test_message.py b/tests/test_message.py index e70b8f0668f..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): @@ -2414,7 +2418,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 +2437,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): diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index 89892741bd4..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 } @@ -143,6 +145,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"}, } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy