diff --git a/README.rst b/README.rst
index dbf6ee3f6..1d0a13b8f 100644
--- a/README.rst
+++ b/README.rst
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
-.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
+.. image:: https://img.shields.io/badge/Bot%20API-6.2-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -93,7 +93,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
-All types and methods of the Telegram Bot API **6.1** are supported.
+All types and methods of the Telegram Bot API **6.2** are supported.
Installing
==========
diff --git a/README_RAW.rst b/README_RAW.rst
index 04e1ce75f..1ea1f9af4 100644
--- a/README_RAW.rst
+++ b/README_RAW.rst
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
-.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
+.. image:: https://img.shields.io/badge/Bot%20API-6.2-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -89,7 +89,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
-All types and methods of the Telegram Bot API **6.1** are supported.
+All types and methods of the Telegram Bot API **6.2** are supported.
Installing
==========
diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst
index 282c5669b..3be887306 100644
--- a/docs/source/inclusions/bot_methods.rst
+++ b/docs/source/inclusions/bot_methods.rst
@@ -206,6 +206,8 @@
- Used for getting a sticker set
* - :meth:`~telegram.Bot.upload_sticker_file`
- Used for uploading a sticker file
+ * - :meth:`~telegram.Bot.get_custom_emoji_stickers`
+ - Used for getting custom emoji files based on their IDs
.. raw:: html
diff --git a/telegram/_bot.py b/telegram/_bot.py
index fda5cf2f3..fcfeeaaa0 100644
--- a/telegram/_bot.py
+++ b/telegram/_bot.py
@@ -6217,6 +6217,61 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
)
return StickerSet.de_json(result, self) # type: ignore[return-value, arg-type]
+ @_log
+ async def get_custom_emoji_stickers(
+ self,
+ custom_emoji_ids: List[str],
+ *,
+ read_timeout: ODVInput[float] = DEFAULT_NONE,
+ write_timeout: ODVInput[float] = DEFAULT_NONE,
+ connect_timeout: ODVInput[float] = DEFAULT_NONE,
+ pool_timeout: ODVInput[float] = DEFAULT_NONE,
+ api_kwargs: JSONDict = None,
+ ) -> List[Sticker]:
+ # skipcq: FLK-D207
+ """
+ Use this method to get information about emoji stickers by their identifiers.
+
+ Args:
+ custom_emoji_ids (List[:obj:`str`]): List of custom emoji identifiers.
+ At most :tg-const:`telegram.constants.CustomEmojiStickerLimit.\
+CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
+
+ Keyword Args:
+ read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
+ :paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
+ :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
+ write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
+ :paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to
+ :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
+ connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
+ :paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to
+ :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
+ pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
+ :paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to
+ :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
+ api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
+ Telegram API.
+
+ Returns:
+ List[:class:`telegram.Sticker`]
+
+ Raises:
+ :class:`telegram.error.TelegramError`
+
+ """
+ data: JSONDict = {"custom_emoji_ids": custom_emoji_ids}
+ result = await self._post(
+ "getCustomEmojiStickers",
+ data,
+ read_timeout=read_timeout,
+ write_timeout=write_timeout,
+ connect_timeout=connect_timeout,
+ pool_timeout=pool_timeout,
+ api_kwargs=api_kwargs,
+ )
+ return Sticker.de_list(result, self) # type: ignore[return-value, arg-type]
+
@_log
async def upload_sticker_file(
self,
@@ -6290,10 +6345,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
title: str,
emojis: str,
png_sticker: FileInput = None,
- contains_masks: bool = None,
mask_position: MaskPosition = None,
tgs_sticker: FileInput = None,
webm_sticker: FileInput = None,
+ sticker_type: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -6304,8 +6359,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to create new sticker set owned by a user.
The bot will be able to edit the created sticker set.
- You must use exactly one of the fields :paramref:`png_sticker`, :paramref:`tgs_sticker`, or
- :paramref:`webm_sticker`.
+ You must use exactly one of the fields :paramref:`png_sticker`, :paramref:`tgs_sticker`,
+ or :paramref:`webm_sticker`.
Warning:
As of API 4.7 :paramref:`png_sticker` is an optional argument and therefore the order
@@ -6316,6 +6371,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
The :paramref:`png_sticker` and :paramref:`tgs_sticker` argument can be either a
file_id, an URL or a file from disk ``open(filename, 'rb')``
+ .. versionchanged:: 20.0
+ The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type`
+ instead.
+
Args:
user_id (:obj:`int`): User identifier of created sticker set owner.
name (:obj:`str`): Short name of sticker set, to be used in t.me/addstickers/ URLs
@@ -6349,10 +6408,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
.. versionadded:: 13.11
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
- contains_masks (:obj:`bool`, optional): Pass :obj:`True`, if a set of mask stickers
- should be created.
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
should be placed on faces.
+ sticker_type (:obj:`str`, optional): Type of stickers in the set, pass
+ :attr:`telegram.Sticker.REGULAR` or :attr:`telegram.Sticker.MASK`. Custom emoji
+ sticker sets can't be created via the Bot API at the moment. By default, a
+ regular sticker set is created.
+
+ .. versionadded:: 20.0
Keyword Args:
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
@@ -6385,10 +6448,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
data["tgs_sticker"] = parse_file_input(tgs_sticker)
if webm_sticker is not None:
data["webm_sticker"] = parse_file_input(webm_sticker)
- if contains_masks is not None:
- data["contains_masks"] = contains_masks
if mask_position is not None:
data["mask_position"] = mask_position
+ if sticker_type is not None:
+ data["sticker_type"] = sticker_type
result = await self._post(
"createNewStickerSet",
@@ -8034,6 +8097,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""Alias for :meth:`unpin_chat_message`"""
unpinAllChatMessages = unpin_all_chat_messages
"""Alias for :meth:`unpin_all_chat_messages`"""
+ getCustomEmojiStickers = get_custom_emoji_stickers
+ """Alias for :meth:`get_custom_emoji_stickers`"""
getStickerSet = get_sticker_set
"""Alias for :meth:`get_sticker_set`"""
uploadStickerFile = upload_sticker_file
diff --git a/telegram/_chat.py b/telegram/_chat.py
index 0b7074deb..da82d65a1 100644
--- a/telegram/_chat.py
+++ b/telegram/_chat.py
@@ -134,6 +134,12 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
+ has_restricted_voice_and_video_messages (:obj:`bool`, optional): :obj:`True`, if the
+ privacy settings of the other party restrict sending voice and video note messages
+ in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
+
+ .. versionadded:: 20.0
+
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -187,6 +193,11 @@ class Chat(TelegramObject):
joining the supergroup need to be approved by supergroup administrators. Returned only
in :meth:`telegram.Bot.get_chat`.
+ .. versionadded:: 20.0
+ has_restricted_voice_and_video_messages (:obj:`bool`): Optional. :obj:`True`, if the
+ privacy settings of the other party restrict sending voice and video note messages
+ in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
+
.. versionadded:: 20.0
"""
@@ -215,6 +226,7 @@ class Chat(TelegramObject):
"has_private_forwards",
"join_to_send_messages",
"join_by_request",
+ "has_restricted_voice_and_video_messages",
)
SENDER: ClassVar[str] = constants.ChatType.SENDER
@@ -256,6 +268,7 @@ class Chat(TelegramObject):
has_protected_content: bool = None,
join_to_send_messages: bool = None,
join_by_request: bool = None,
+ has_restricted_voice_and_video_messages: bool = None,
**_kwargs: Any,
):
# Required
@@ -286,6 +299,7 @@ class Chat(TelegramObject):
self.location = location
self.join_to_send_messages = join_to_send_messages
self.join_by_request = join_by_request
+ self.has_restricted_voice_and_video_messages = has_restricted_voice_and_video_messages
self.set_bot(bot)
self._id_attrs = (self.id,)
diff --git a/telegram/_files/sticker.py b/telegram/_files/sticker.py
index 564e9e5c4..45ddea8bb 100644
--- a/telegram/_files/sticker.py
+++ b/telegram/_files/sticker.py
@@ -54,6 +54,11 @@ class Sticker(_BaseThumbedMedium):
is_video (:obj:`bool`): :obj:`True`, if the sticker is a video sticker.
.. versionadded:: 13.11
+ type (:obj:`str`): Type of the sticker. Currently one of :attr:`REGULAR`,
+ :attr:`MASK`, :attr:`CUSTOM_EMOJI`. The type of the sticker is independent from its
+ format, which is determined by the fields :attr:`is_animated` and :attr:`is_video`.
+
+ .. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
emoji (:obj:`str`, optional): Emoji associated with the sticker
@@ -63,8 +68,12 @@ class Sticker(_BaseThumbedMedium):
position where the mask should be placed.
file_size (:obj:`int`, optional): File size in bytes.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
- premium_animation (:class:`telegram.File`, optional): Premium animation for the sticker,
- if the sticker is premium.
+ premium_animation (:class:`telegram.File`, optional): For premium regular stickers,
+ premium animation for the sticker.
+
+ .. versionadded:: 20.0
+ custom_emoji (:obj:`str`, optional): For custom emoji stickers, unique identifier of the
+ custom emoji.
.. versionadded:: 20.0
_kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -80,6 +89,11 @@ class Sticker(_BaseThumbedMedium):
is_video (:obj:`bool`): :obj:`True`, if the sticker is a video sticker.
.. versionadded:: 13.11
+ type (:obj:`str`): Type of the sticker. Currently one of :attr:`REGULAR`,
+ :attr:`MASK`, :attr:`CUSTOM_EMOJI`. The type of the sticker is independent from its
+ format, which is determined by the fields :attr:`is_animated` and :attr:`is_video`.
+
+ .. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
emoji (:obj:`str`): Optional. Emoji associated with the sticker.
@@ -88,11 +102,14 @@ class Sticker(_BaseThumbedMedium):
where the mask should be placed.
file_size (:obj:`int`): Optional. File size in bytes.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
- premium_animation (:class:`telegram.File`): Optional. Premium animation for the
- sticker, if the sticker is premium.
+ premium_animation (:class:`telegram.File`): Optional. For premium regular stickers,
+ premium animation for the sticker.
.. versionadded:: 20.0
+ custom_emoji (:obj:`str`): Optional. For custom emoji stickers, unique identifier of the
+ custom emoji.
+ .. versionadded:: 20.0
"""
__slots__ = (
@@ -104,6 +121,8 @@ class Sticker(_BaseThumbedMedium):
"set_name",
"width",
"premium_animation",
+ "type",
+ "custom_emoji_id",
)
def __init__(
@@ -114,6 +133,7 @@ class Sticker(_BaseThumbedMedium):
height: int,
is_animated: bool,
is_video: bool,
+ type: str, # pylint: disable=redefined-builtin
thumb: PhotoSize = None,
emoji: str = None,
file_size: int = None,
@@ -121,6 +141,7 @@ class Sticker(_BaseThumbedMedium):
mask_position: "MaskPosition" = None,
bot: "Bot" = None,
premium_animation: "File" = None,
+ custom_emoji_id: str = None,
**_kwargs: Any,
):
super().__init__(
@@ -135,11 +156,20 @@ class Sticker(_BaseThumbedMedium):
self.height = height
self.is_animated = is_animated
self.is_video = is_video
+ self.type = type
# Optional
self.emoji = emoji
self.set_name = set_name
self.mask_position = mask_position
self.premium_animation = premium_animation
+ self.custom_emoji_id = custom_emoji_id
+
+ REGULAR: ClassVar[str] = constants.StickerType.REGULAR
+ """:const:`telegram.constants.StickerType.REGULAR`"""
+ MASK: ClassVar[str] = constants.StickerType.MASK
+ """:const:`telegram.constants.StickerType.MASK`"""
+ CUSTOM_EMOJI: ClassVar[str] = constants.StickerType.CUSTOM_EMOJI
+ """:const:`telegram.constants.StickerType.CUSTOM_EMOJI`"""
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Sticker"]:
@@ -167,6 +197,9 @@ class StickerSet(TelegramObject):
arguments had to be changed. Use keyword arguments to make sure that the arguments are
passed correctly.
+ .. versionchanged:: 20.0:
+ The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type` instead.
+
Args:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
@@ -174,8 +207,12 @@ class StickerSet(TelegramObject):
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
- contains_masks (:obj:`bool`): :obj:`True`, if the sticker set contains masks.
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
+ sticker_type (:obj:`str`): Type of stickers in the set, currently one of
+ :attr:`telegram.Sticker.REGULAR`, :attr:`telegram.Sticker.MASK`,
+ :attr:`telegram.Sticker.CUSTOM_EMOJI`.
+
+ .. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
@@ -186,21 +223,23 @@ class StickerSet(TelegramObject):
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
- contains_masks (:obj:`bool`): :obj:`True`, if the sticker set contains masks.
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
+ sticker_type (:obj:`str`): Type of stickers in the set.
+
+ .. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``,
``.TGS`` or ``.WEBM`` format.
"""
__slots__ = (
- "contains_masks",
"is_animated",
"is_video",
"name",
"stickers",
"thumb",
"title",
+ "sticker_type",
)
def __init__(
@@ -208,9 +247,9 @@ class StickerSet(TelegramObject):
name: str,
title: str,
is_animated: bool,
- contains_masks: bool,
stickers: List[Sticker],
is_video: bool,
+ sticker_type: str,
thumb: PhotoSize = None,
**_kwargs: Any,
):
@@ -218,8 +257,8 @@ class StickerSet(TelegramObject):
self.title = title
self.is_animated = is_animated
self.is_video = is_video
- self.contains_masks = contains_masks
self.stickers = stickers
+ self.sticker_type = sticker_type
# Optional
self.thumb = thumb
diff --git a/telegram/_message.py b/telegram/_message.py
index d3fa198f6..2c80aaa62 100644
--- a/telegram/_message.py
+++ b/telegram/_message.py
@@ -370,6 +370,8 @@ class Message(TelegramObject):
to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
+ .. |custom_emoji_formatting_note| replace:: Custom emoji entities will currently be ignored
+ by this function. Instead, the supplied replacement for the emoji will be used.
"""
# fmt: on
@@ -2864,6 +2866,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML in
the same way the original message was formatted.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
@@ -2880,6 +2885,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
@@ -2897,6 +2905,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML in the same way the original message was formatted.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
@@ -2913,6 +2924,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
@@ -3093,6 +3107,8 @@ class Message(TelegramObject):
:tg-const:`telegram.constants.ParseMode.MARKDOWN` is a legacy mode, retained by
Telegram for backward compatibility. You should use :meth:`text_markdown_v2` instead.
+ |custom_emoji_formatting_note|
+
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@@ -3111,6 +3127,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown
in the same way the original message was formatted.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
@@ -3132,6 +3151,8 @@ class Message(TelegramObject):
Telegram for backward compatibility. You should use :meth:`text_markdown_v2_urled`
instead.
+ |custom_emoji_formatting_note|
+
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@@ -3150,6 +3171,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
@@ -3171,6 +3195,8 @@ class Message(TelegramObject):
Telegram for backward compatibility. You should use :meth:`caption_markdown_v2`
instead.
+ |custom_emoji_formatting_note|
+
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@@ -3189,6 +3215,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown in the same way the original message was formatted.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
@@ -3212,6 +3241,8 @@ class Message(TelegramObject):
Telegram for backward compatibility. You should use :meth:`caption_markdown_v2_urled`
instead.
+ |custom_emoji_formatting_note|
+
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@@ -3230,6 +3261,9 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
+ Note:
+ |custom_emoji_formatting_note|
+
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
diff --git a/telegram/_messageentity.py b/telegram/_messageentity.py
index 1235ed86e..0debe9adf 100644
--- a/telegram/_messageentity.py
+++ b/telegram/_messageentity.py
@@ -44,7 +44,11 @@ class MessageEntity(TelegramObject):
:attr:`URL`, :attr:`EMAIL`, :attr:`PHONE_NUMBER`, :attr:`BOLD` (bold text),
:attr:`ITALIC` (italic text), :attr:`STRIKETHROUGH`, :attr:`SPOILER` (spoiler message),
:attr:`CODE` (monowidth string), :attr:`PRE` (monowidth block), :attr:`TEXT_LINK` (for
- clickable text URLs), :attr:`TEXT_MENTION` (for users without usernames).
+ clickable text URLs), :attr:`TEXT_MENTION` (for users without usernames),
+ :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers).
+
+ .. versionadded:: 20.0
+ added inline custom emoji
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
length (:obj:`int`): Length of the entity in UTF-16 code units.
url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after
@@ -53,6 +57,11 @@ class MessageEntity(TelegramObject):
user.
language (:obj:`str`, optional): For :attr:`PRE` only, the programming language of
the entity text.
+ custom_emoji_id (:obj:`str`, optional): For :attr:`CUSTOM_EMOJI` only, unique identifier
+ of the custom emoji. Use :meth:`telegram.Bot.get_custom_emoji_stickers` to get full
+ information about the sticker.
+
+ .. versionadded:: 20.0
Attributes:
type (:obj:`str`): Type of the entity.
@@ -61,10 +70,13 @@ class MessageEntity(TelegramObject):
url (:obj:`str`): Optional. Url that will be opened after user taps on the text.
user (:class:`telegram.User`): Optional. The mentioned user.
language (:obj:`str`): Optional. Programming language of the entity text.
+ custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji.
+
+ .. versionadded:: 20.0
"""
- __slots__ = ("length", "url", "user", "type", "language", "offset")
+ __slots__ = ("length", "url", "user", "type", "language", "offset", "custom_emoji_id")
def __init__(
self,
@@ -74,6 +86,7 @@ class MessageEntity(TelegramObject):
url: str = None,
user: User = None,
language: str = None,
+ custom_emoji_id: str = None,
**_kwargs: Any,
):
# Required
@@ -84,6 +97,7 @@ class MessageEntity(TelegramObject):
self.url = url
self.user = user
self.language = language
+ self.custom_emoji_id = custom_emoji_id
self._id_attrs = (self.type, self.offset, self.length)
@@ -134,5 +148,10 @@ class MessageEntity(TelegramObject):
.. versionadded:: 13.10
"""
+ CUSTOM_EMOJI: ClassVar[str] = constants.MessageEntityType.CUSTOM_EMOJI
+ """:const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI`
+
+ .. versionadded:: 20.0
+ """
ALL_TYPES: ClassVar[List[str]] = list(constants.MessageEntityType)
"""List[:obj:`str`]: A list of all available message entity types."""
diff --git a/telegram/constants.py b/telegram/constants.py
index 57b378132..db3c072fa 100644
--- a/telegram/constants.py
+++ b/telegram/constants.py
@@ -39,6 +39,7 @@ __all__ = [
"ChatInviteLinkLimit",
"ChatMemberStatus",
"ChatType",
+ "CustomEmojiStickerLimit",
"DiceEmoji",
"FileSizeLimit",
"FloodLimit",
@@ -58,6 +59,7 @@ __all__ = [
"PollLimit",
"PollType",
"SUPPORTED_WEBHOOK_PORTS",
+ "StickerType",
"WebhookLimit",
"UpdateType",
]
@@ -92,7 +94,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
-BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=1)
+BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=2)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -277,6 +279,22 @@ class ChatType(StringEnum):
""":obj:`str`: A :class:`telegram.Chat` that is a channel."""
+class CustomEmojiStickerLimit(IntEnum):
+ """This enum contains limitations for :meth:`telegram.Bot.get_custom_emoji_stickers`.
+ The enum members of this enumeration are instances of :class:`int` and can be treated as such.
+
+ .. versionadded:: 20.0
+ """
+
+ __slots__ = ()
+
+ CUSTOM_EMOJI_IDENTIFIER_LIMIT = 200
+ """:obj:`int`: Maximum amount of custom emoji identifiers which can be specified for the
+ :paramref:`~telegram.Bot.get_custom_emoji_stickers.custom_emoji_ids` parameter of
+ :meth:`telegram.Bot.get_custom_emoji_stickers`.
+ """
+
+
class DiceEmoji(StringEnum):
"""This enum contains the available emoji for :class:`telegram.Dice`/
:meth:`telegram.Bot.send_dice`. The enum
@@ -605,6 +623,11 @@ class MessageEntityType(StringEnum):
""":obj:`str`: Message entities representing strikethrough text."""
SPOILER = "spoiler"
""":obj:`str`: Message entities representing spoiler text."""
+ CUSTOM_EMOJI = "custom_emoji"
+ """:obj:`str`: Message entities representing inline custom emoji stickers.
+
+ .. versionadded:: 20.0
+ """
class MessageLimit(IntEnum):
@@ -718,6 +741,23 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.video_chat_participants_invited`."""
+class StickerType(StringEnum):
+ """This enum contains the available types of :class:`telegram.Sticker`. The enum
+ members of this enumeration are instances of :class:`str` and can be treated as such.
+
+ .. versionadded:: 20.0
+ """
+
+ __slots__ = ()
+
+ REGULAR = "regular"
+ """:obj:`str`: Regular sticker."""
+ MASK = "mask"
+ """:obj:`str`: Mask sticker."""
+ CUSTOM_EMOJI = "custom_emoji"
+ """:obj:`str`: Custom emoji sticker."""
+
+
class ParseMode(StringEnum):
"""This enum contains the available parse modes. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
index 3c730fc90..5a705ee3f 100644
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -1965,6 +1965,7 @@ class Sticker:
.. versionadded:: 20.0
"""
+ # neither mask nor emoji can be a message.sticker, so no filters for them
class _SuccessfulPayment(MessageFilter):
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 402dbd383..a0b585373 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -2239,8 +2239,8 @@ class TestBot:
)
# get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set,
- # set_sticker_position_in_set and delete_sticker_from_set are tested in the
- # test_sticker module.
+ # set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers
+ # are tested in the test_sticker module.
async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id):
# Use BaseException that's not a subclass of Exception such that
diff --git a/tests/test_chat.py b/tests/test_chat.py
index 979ce01b2..b3c89eff8 100644
--- a/tests/test_chat.py
+++ b/tests/test_chat.py
@@ -44,6 +44,7 @@ def chat(bot):
has_protected_content=True,
join_to_send_messages=True,
join_by_request=True,
+ has_restricted_voice_and_video_messages=True,
)
@@ -68,6 +69,7 @@ class TestChat:
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
+ has_restricted_voice_and_video_messages = True
def test_slot_behaviour(self, chat, mro_slots):
for attr in chat.__slots__:
@@ -92,6 +94,9 @@ class TestChat:
"location": self.location.to_dict(),
"join_to_send_messages": self.join_to_send_messages,
"join_by_request": self.join_by_request,
+ "has_restricted_voice_and_video_messages": (
+ self.has_restricted_voice_and_video_messages
+ ),
}
chat = Chat.de_json(json_dict, bot)
@@ -112,6 +117,10 @@ class TestChat:
assert chat.location.address == self.location.address
assert chat.join_to_send_messages == self.join_to_send_messages
assert chat.join_by_request == self.join_by_request
+ assert (
+ chat.has_restricted_voice_and_video_messages
+ == self.has_restricted_voice_and_video_messages
+ )
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@@ -131,6 +140,10 @@ class TestChat:
assert chat_dict["location"] == chat.location.to_dict()
assert chat_dict["join_to_send_messages"] == chat.join_to_send_messages
assert chat_dict["join_by_request"] == chat.join_by_request
+ assert (
+ chat_dict["has_restricted_voice_and_video_messages"]
+ == chat.has_restricted_voice_and_video_messages
+ )
def test_enum_init(self):
chat = Chat(id=1, type="foo")
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 23e285e28..a3fcb5c32 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -828,7 +828,7 @@ class TestFilters:
def test_filters_sticker(self, update):
assert not filters.Sticker.ALL.check_update(update)
- update.message.sticker = Sticker("1", "uniq", 1, 2, False, False)
+ update.message.sticker = Sticker("1", "uniq", 1, 2, False, False, Sticker.REGULAR)
assert filters.Sticker.ALL.check_update(update)
assert filters.Sticker.STATIC.check_update(update)
assert not filters.Sticker.VIDEO.check_update(update)
diff --git a/tests/test_message.py b/tests/test_message.py
index 1e904f457..8f3fb6fcf 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -105,7 +105,7 @@ def message(bot):
)
},
{"photo": [PhotoSize("photo_id", "unique_id", 50, 50)], "caption": "photo_file"},
- {"sticker": Sticker("sticker_id", "unique_id", 50, 50, True, False)},
+ {"sticker": Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR)},
{"video": Video("video_id", "unique_id", 12, 12, 12), "caption": "video_file"},
{"voice": Voice("voice_id", "unique_id", 5)},
{"video_note": VideoNote("video_note_id", "unique_id", 20, 12)},
@@ -542,6 +542,36 @@ class TestMessage:
)
assert expected == message.text_markdown
+ @pytest.mark.parametrize(
+ "type_",
+ argvalues=[
+ "text_html",
+ "text_html_urled",
+ "text_markdown",
+ "text_markdown_urled",
+ "text_markdown_v2",
+ "text_markdown_v2_urled",
+ ],
+ )
+ def test_text_custom_emoji(self, type_):
+ text = "Look a custom emoji: 😎"
+ expected = "Look a custom emoji: 😎"
+ emoji_entity = MessageEntity(
+ type=MessageEntity.CUSTOM_EMOJI,
+ offset=21,
+ length=2,
+ custom_emoji_id="5472409228461217725",
+ )
+ message = Message(
+ 1,
+ from_user=self.from_user,
+ date=self.date,
+ chat=self.chat,
+ text=text,
+ entities=[emoji_entity],
+ )
+ assert expected == message[type_]
+
def test_caption_html_simple(self):
test_html_string = (
"Test for <bold, ita_lic, "
@@ -651,6 +681,36 @@ class TestMessage:
)
assert expected == message.caption_markdown
+ @pytest.mark.parametrize(
+ "type_",
+ argvalues=[
+ "caption_html",
+ "caption_html_urled",
+ "caption_markdown",
+ "caption_markdown_urled",
+ "caption_markdown_v2",
+ "caption_markdown_v2_urled",
+ ],
+ )
+ def test_caption_custom_emoji(self, type_):
+ caption = "Look a custom emoji: 😎"
+ expected = "Look a custom emoji: 😎"
+ emoji_entity = MessageEntity(
+ type=MessageEntity.CUSTOM_EMOJI,
+ offset=21,
+ length=2,
+ custom_emoji_id="5472409228461217725",
+ )
+ message = Message(
+ 1,
+ from_user=self.from_user,
+ date=self.date,
+ chat=self.chat,
+ caption=caption,
+ caption_entities=[emoji_entity],
+ )
+ assert expected == message[type_]
+
async def test_parse_entities_url_emoji(self):
url = b"http://github.com/?unicode=\\u2713\\U0001f469".decode("unicode-escape")
text = "some url"
diff --git a/tests/test_photo.py b/tests/test_photo.py
index 3c42ba9f2..7098bd09a 100644
--- a/tests/test_photo.py
+++ b/tests/test_photo.py
@@ -468,7 +468,15 @@ class TestPhoto:
b = PhotoSize("", photo.file_unique_id, self.width, self.height)
c = PhotoSize(photo.file_id, photo.file_unique_id, 0, 0)
d = PhotoSize("", "", self.width, self.height)
- e = Sticker(photo.file_id, photo.file_unique_id, self.width, self.height, False, False)
+ e = Sticker(
+ photo.file_id,
+ photo.file_unique_id,
+ self.width,
+ self.height,
+ False,
+ False,
+ Sticker.REGULAR,
+ )
assert a == b
assert hash(a) == hash(b)
diff --git a/tests/test_sticker.py b/tests/test_sticker.py
index f44f1556e..80fc47f26 100644
--- a/tests/test_sticker.py
+++ b/tests/test_sticker.py
@@ -87,6 +87,8 @@ class TestSticker:
thumb_width = 319
thumb_height = 320
thumb_file_size = 21472
+ type = Sticker.REGULAR
+ custom_emoji_id = "ThisIsSuchACustomEmojiID"
sticker_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
sticker_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
@@ -120,6 +122,7 @@ class TestSticker:
assert sticker.thumb.width == self.thumb_width
assert sticker.thumb.height == self.thumb_height
assert sticker.thumb.file_size == self.thumb_file_size
+ assert sticker.type == self.type
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert sticker.premium_animation == self.premium_animation
@@ -139,6 +142,8 @@ class TestSticker:
assert message.sticker.is_animated == sticker.is_animated
assert message.sticker.is_video == sticker.is_video
assert message.sticker.file_size == sticker.file_size
+ assert message.sticker.type == sticker.type
+ assert message.has_protected_content
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert message.sticker.premium_animation == sticker.premium_animation
@@ -150,7 +155,6 @@ class TestSticker:
assert message.sticker.thumb.width == sticker.thumb.width
assert message.sticker.thumb.height == sticker.thumb.height
assert message.sticker.thumb.file_size == sticker.thumb.file_size
- assert message.has_protected_content
@flaky(3, 1)
async def test_get_and_download(self, bot, sticker):
@@ -197,6 +201,7 @@ class TestSticker:
assert message.sticker.is_animated == sticker.is_animated
assert message.sticker.is_video == sticker.is_video
assert message.sticker.file_size == sticker.file_size
+ assert message.sticker.type == sticker.type
assert isinstance(message.sticker.thumb, PhotoSize)
assert isinstance(message.sticker.thumb.file_id, str)
@@ -219,6 +224,8 @@ class TestSticker:
"emoji": self.emoji,
"file_size": self.file_size,
"premium_animation": self.premium_animation.to_dict(),
+ "type": self.type,
+ "custom_emoji_id": self.custom_emoji_id,
}
json_sticker = Sticker.de_json(json_dict, bot)
@@ -232,6 +239,8 @@ class TestSticker:
assert json_sticker.file_size == self.file_size
assert json_sticker.thumb == sticker.thumb
assert json_sticker.premium_animation == self.premium_animation
+ assert json_sticker.type == self.type
+ assert json_sticker.custom_emoji_id == self.custom_emoji_id
async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
@@ -310,6 +319,7 @@ class TestSticker:
assert sticker_dict["is_video"] == sticker.is_video
assert sticker_dict["file_size"] == sticker.file_size
assert sticker_dict["thumb"] == sticker.thumb.to_dict()
+ assert sticker_dict["type"] == sticker.type
@flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
@@ -343,6 +353,16 @@ class TestSticker:
}
assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict
+ @flaky(3, 1)
+ async def test_custom_emoji(self, bot):
+ # testing custom emoji stickers is as much of an annoyance as the premium animation, see
+ # in test_premium_animation
+ custom_emoji_set = await bot.get_sticker_set("PTBStaticEmojiTestPack")
+ # the first one to appear here is a sticker with unique file id of AQADjBsAAkKD0Uty
+ # this could change in the future ofc.
+ custom_emoji_sticker = custom_emoji_set.stickers[0]
+ assert custom_emoji_sticker.custom_emoji_id == "6046140249875156202"
+
def test_equality(self, sticker):
a = Sticker(
sticker.file_id,
@@ -351,14 +371,41 @@ class TestSticker:
self.height,
self.is_animated,
self.is_video,
+ self.type,
)
b = Sticker(
- "", sticker.file_unique_id, self.width, self.height, self.is_animated, self.is_video
+ "",
+ sticker.file_unique_id,
+ self.width,
+ self.height,
+ self.is_animated,
+ self.is_video,
+ self.type,
+ )
+ c = Sticker(
+ sticker.file_id,
+ sticker.file_unique_id,
+ 0,
+ 0,
+ False,
+ True,
+ self.type,
+ )
+ d = Sticker(
+ "",
+ "",
+ self.width,
+ self.height,
+ self.is_animated,
+ self.is_video,
+ self.type,
)
- c = Sticker(sticker.file_id, sticker.file_unique_id, 0, 0, False, True)
- d = Sticker("", "", self.width, self.height, self.is_animated, self.is_video)
e = PhotoSize(
- sticker.file_id, sticker.file_unique_id, self.width, self.height, self.is_animated
+ sticker.file_id,
+ sticker.file_unique_id,
+ self.width,
+ self.height,
+ self.is_animated,
)
assert a == b
@@ -427,9 +474,9 @@ class TestStickerSet:
title = "Test stickers"
is_animated = True
is_video = True
- contains_masks = False
- stickers = [Sticker("file_id", "file_un_id", 512, 512, True, True)]
+ stickers = [Sticker("file_id", "file_un_id", 512, 512, True, True, Sticker.REGULAR)]
name = "NOTAREALNAME"
+ sticker_type = Sticker.REGULAR
def test_de_json(self, bot, sticker):
name = f"test_by_{bot.username}"
@@ -438,9 +485,9 @@ class TestStickerSet:
"title": self.title,
"is_animated": self.is_animated,
"is_video": self.is_video,
- "contains_masks": self.contains_masks,
"stickers": [x.to_dict() for x in self.stickers],
"thumb": sticker.thumb.to_dict(),
+ "sticker_type": self.sticker_type,
}
sticker_set = StickerSet.de_json(json_dict, bot)
@@ -448,9 +495,9 @@ class TestStickerSet:
assert sticker_set.title == self.title
assert sticker_set.is_animated == self.is_animated
assert sticker_set.is_video == self.is_video
- assert sticker_set.contains_masks == self.contains_masks
assert sticker_set.stickers == self.stickers
assert sticker_set.thumb == sticker.thumb
+ assert sticker_set.sticker_type == self.sticker_type
async def test_create_sticker_set(
self, bot, chat_id, sticker_file, animated_sticker_file, video_sticker_file
@@ -536,8 +583,9 @@ class TestStickerSet:
assert sticker_set_dict["title"] == sticker_set.title
assert sticker_set_dict["is_animated"] == sticker_set.is_animated
assert sticker_set_dict["is_video"] == sticker_set.is_video
- assert sticker_set_dict["contains_masks"] == sticker_set.contains_masks
assert sticker_set_dict["stickers"][0] == sticker_set.stickers[0].to_dict()
+ assert sticker_set_dict["thumb"] == sticker_set.thumb.to_dict()
+ assert sticker_set_dict["sticker_type"] == sticker_set.sticker_type
@flaky(3, 1)
async def test_bot_methods_2_png(self, bot, sticker_set):
@@ -639,6 +687,32 @@ class TestStickerSet:
assert test_flag
monkeypatch.delattr(bot, "_post")
+ async def test_create_new_sticker_all_params(self, monkeypatch, bot, chat_id, mask_position):
+ async def make_assertion(_, data, *args, **kwargs):
+ assert data["user_id"] == chat_id
+ assert data["name"] == "name"
+ assert data["title"] == "title"
+ assert data["emojis"] == "emoji"
+ assert data["mask_position"] == mask_position
+ assert data["png_sticker"] == "wow.png"
+ assert data["tgs_sticker"] == "wow.tgs"
+ assert data["webm_sticker"] == "wow.webm"
+ assert data["sticker_type"] == Sticker.MASK
+
+ monkeypatch.setattr(bot, "_post", make_assertion)
+ await bot.create_new_sticker_set(
+ chat_id,
+ "name",
+ "title",
+ "emoji",
+ mask_position=mask_position,
+ png_sticker="wow.png",
+ tgs_sticker="wow.tgs",
+ webm_sticker="wow.webm",
+ sticker_type=Sticker.MASK,
+ )
+ monkeypatch.delattr(bot, "_post")
+
async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
@@ -685,21 +759,26 @@ class TestStickerSet:
self.name,
self.title,
self.is_animated,
- self.contains_masks,
self.stickers,
self.is_video,
+ self.sticker_type,
)
b = StickerSet(
self.name,
self.title,
self.is_animated,
- self.contains_masks,
self.stickers,
self.is_video,
+ self.sticker_type,
)
- c = StickerSet(self.name, None, None, None, None, None)
+ c = StickerSet(self.name, None, None, None, None, Sticker.CUSTOM_EMOJI)
d = StickerSet(
- "blah", self.title, self.is_animated, self.contains_masks, self.stickers, self.is_video
+ "blah",
+ self.title,
+ self.is_animated,
+ self.stickers,
+ self.is_video,
+ self.sticker_type,
)
e = Audio(self.name, "", 0, None, None)
@@ -775,3 +854,23 @@ class TestMaskPosition:
assert a != e
assert hash(a) != hash(e)
+
+
+class TestGetCustomEmojiSticker:
+ async def test_custom_emoji_sticker(self, bot):
+ # we use the same ID as in test_custom_emoji
+ emoji_sticker_list = await bot.get_custom_emoji_stickers(["6046140249875156202"])
+ assert emoji_sticker_list[0].emoji == "😎"
+ assert emoji_sticker_list[0].height == 100
+ assert emoji_sticker_list[0].width == 100
+ assert not emoji_sticker_list[0].is_animated
+ assert not emoji_sticker_list[0].is_video
+ assert emoji_sticker_list[0].set_name == "PTBStaticEmojiTestPack"
+ assert emoji_sticker_list[0].type == Sticker.CUSTOM_EMOJI
+ assert emoji_sticker_list[0].custom_emoji_id == "6046140249875156202"
+ assert emoji_sticker_list[0].thumb.width == 100
+ assert emoji_sticker_list[0].thumb.height == 100
+ assert emoji_sticker_list[0].thumb.file_size == 3614
+ assert emoji_sticker_list[0].thumb.file_unique_id == "AQAD6gwAAoY06FNy"
+ assert emoji_sticker_list[0].file_size == 3678
+ assert emoji_sticker_list[0].file_unique_id == "AgAD6gwAAoY06FM"