diff --git a/README.md b/README.md index 2aa073d..9ef44c8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

A simple, but extensible Python implementation for the Telegram Bot API.

Both synchronous and asynchronous.

-##

Supported Bot API version: 6.1! +##

Supported Bot API version: 6.2!

Official documentation

diff --git a/telebot/__init__.py b/telebot/__init__.py index da3bf26..ee1cc55 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -4190,6 +4190,20 @@ class TeleBot: result = apihelper.get_sticker_set(self.token, name) return types.StickerSet.de_json(result) + def get_custom_emoji_stickers(self, custom_emoji_ids: List[str]) -> List[types.Sticker]: + """ + Use this method to get information about custom emoji stickers by their identifiers. + Returns an Array of Sticker objects. + + :param custom_emoji_ids: List of custom emoji identifiers. At most 200 custom emoji identifiers can be specified. + :type custom_emoji_ids: :obj:`list` of :obj:`str` + + :return: Returns an Array of Sticker objects. + :rtype: :obj:`list` of :class:`telebot.types.Sticker` + """ + result = apihelper.get_custom_emoji_stickers(self.token, custom_emoji_ids) + return [types.Sticker.de_json(sticker) for sticker in result] + def upload_sticker_file(self, user_id: int, png_sticker: Union[Any, str]) -> types.File: """ Use this method to upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet @@ -4217,6 +4231,7 @@ class TeleBot: tgs_sticker: Union[Any, str]=None, webm_sticker: Union[Any, str]=None, contains_masks: Optional[bool]=None, + sticker_type: Optional[str]=None, mask_position: Optional[types.MaskPosition]=None) -> bool: """ Use this method to create new sticker set owned by a user. @@ -4250,18 +4265,28 @@ class TeleBot: :param webm_sticker: WebM animation with the sticker, uploaded using multipart/form-data. :type webm_sticker: :obj:`str` - :param contains_masks: Pass True, if a set of mask stickers should be created + :param contains_masks: Pass True, if a set of mask stickers should be created. Deprecated since Bot API 6.2, + use sticker_type instead. :type contains_masks: :obj:`bool` + :param sticker_type: Optional, Type of stickers in the set, pass “regular” or “mask”. Custom emoji sticker sets can't be created + via the Bot API at the moment. By default, a regular sticker set is created. + :type sticker_type: :obj:`str` + :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :type mask_position: :class:`telebot.types.MaskPosition` :return: On success, True is returned. :rtype: :obj:`bool` """ + if contains_masks is not None: + logger.warning('The parameter "contains_masks" is deprecated, use "sticker_type" instead') + if sticker_type is None: + sticker_type = 'mask' if contains_masks else 'regular' + return apihelper.create_new_sticker_set( self.token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks, mask_position, webm_sticker) + mask_position, webm_sticker, sticker_type) def add_sticker_to_set( self, user_id: int, name: str, emojis: str, diff --git a/telebot/apihelper.py b/telebot/apihelper.py index b4e89f7..b4f2b8c 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1561,7 +1561,10 @@ def get_sticker_set(token, name): method_url = 'getStickerSet' return _make_request(token, method_url, params={'name': name}) - +def get_custom_emoji_stickers(token, custom_emoji_ids): + method_url = r'getCustomEmojiStickers' + return _make_request(token, method_url, params={'custom_emoji_ids': custom_emoji_ids}) + def upload_sticker_file(token, user_id, png_sticker): method_url = 'uploadStickerFile' payload = {'user_id': user_id} @@ -1571,7 +1574,7 @@ def upload_sticker_file(token, user_id, png_sticker): def create_new_sticker_set( token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks=None, mask_position=None, webm_sticker=None): + mask_position=None, webm_sticker=None, sticker_type=None): method_url = 'createNewStickerSet' payload = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis} if png_sticker: @@ -1586,12 +1589,12 @@ def create_new_sticker_set( files = {stype: sticker} else: payload[stype] = sticker - if contains_masks is not None: - payload['contains_masks'] = contains_masks if mask_position: payload['mask_position'] = mask_position.to_json() if webm_sticker: payload['webm_sticker'] = webm_sticker + if sticker_type: + payload['sticker_type'] = sticker_type return _make_request(token, method_url, params=payload, files=files, method='post') diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 1ae0de5..f8be968 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -5028,6 +5028,20 @@ class AsyncTeleBot: result = await asyncio_helper.get_sticker_set(self.token, name) return types.StickerSet.de_json(result) + async def get_custom_emoji_stickers(self, custom_emoji_ids: List[str]) -> List[types.Sticker]: + """ + Use this method to get information about custom emoji stickers by their identifiers. + Returns an Array of Sticker objects. + + :param custom_emoji_ids: List of custom emoji identifiers. At most 200 custom emoji identifiers can be specified. + :type custom_emoji_ids: :obj:`list` of :obj:`str` + + :return: Returns an Array of Sticker objects. + :rtype: :obj:`list` of :class:`telebot.types.Sticker` + """ + result = asyncio_helper.get_custom_emoji_stickers(self.token, custom_emoji_ids) + return [types.Sticker.de_json(sticker) for sticker in result] + async def upload_sticker_file(self, user_id: int, png_sticker: Union[Any, str]) -> types.File: """ Use this method to upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet @@ -5055,6 +5069,7 @@ class AsyncTeleBot: tgs_sticker: Union[Any, str]=None, webm_sticker: Union[Any, str]=None, contains_masks: Optional[bool]=None, + sticker_type: Optional[str]=None, mask_position: Optional[types.MaskPosition]=None) -> bool: """ Use this method to create new sticker set owned by a user. @@ -5088,18 +5103,28 @@ class AsyncTeleBot: :param webm_sticker: WebM animation with the sticker, uploaded using multipart/form-data. :type webm_sticker: :obj:`str` - :param contains_masks: Pass True, if a set of mask stickers should be created + :param contains_masks: Pass True, if a set of mask stickers should be created. Deprecated since Bot API 6.2, + use sticker_type instead. :type contains_masks: :obj:`bool` + :param sticker_type: Optional, Type of stickers in the set, pass “regular” or “mask”. Custom emoji sticker sets can't be created + via the Bot API at the moment. By default, a regular sticker set is created. + :type sticker_type: :obj:`str` + :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :type mask_position: :class:`telebot.types.MaskPosition` :return: On success, True is returned. :rtype: :obj:`bool` """ + if contains_masks is not None: + logger.warning('The parameter "contains_masks" is deprecated, use "sticker_type" instead') + if sticker_type is None: + sticker_type = 'mask' if contains_masks else 'regular' + return await asyncio_helper.create_new_sticker_set( self.token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks, mask_position, webm_sticker) + mask_position, webm_sticker, sticker_type) async def add_sticker_to_set( diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 8141657..af537ca 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1532,6 +1532,9 @@ async def get_sticker_set(token, name): method_url = 'getStickerSet' return await _process_request(token, method_url, params={'name': name}) +async def get_custom_emoji_stickers(token, custom_emoji_ids): + method_url = r'getCustomEmojiStickers' + return _process_request(token, method_url, params={'custom_emoji_ids': custom_emoji_ids}) async def upload_sticker_file(token, user_id, png_sticker): method_url = 'uploadStickerFile' @@ -1542,7 +1545,7 @@ async def upload_sticker_file(token, user_id, png_sticker): async def create_new_sticker_set( token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks=None, mask_position=None, webm_sticker=None): + mask_position=None, webm_sticker=None, sticker_type=None): method_url = 'createNewStickerSet' payload = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis} if png_sticker: @@ -1557,12 +1560,12 @@ async def create_new_sticker_set( files = {stype: sticker} else: payload[stype] = sticker - if contains_masks is not None: - payload['contains_masks'] = contains_masks if mask_position: payload['mask_position'] = mask_position.to_json() if webm_sticker: payload['webm_sticker'] = webm_sticker + if sticker_type: + payload['sticker_type'] = sticker_type return await _process_request(token, method_url, params=payload, files=files, method='post') diff --git a/telebot/types.py b/telebot/types.py index 0315a36..27c8ed8 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -525,6 +525,10 @@ class Chat(JsonDeserializable): allows to use tg://user?id= links only in chats with the user. Returned only in getChat. :type has_private_forwards: :obj:`bool` + :param has_restricted_voice_and_video_messages: Optional. True, if the privacy settings of the other party restrict sending voice and video note messages + in the private chat. Returned only in getChat. + :type :obj:`bool` + :param join_to_send_messages: Optional. :obj:`bool`, if users need to join the supergroup before they can send messages. Returned only in getChat. :type join_to_send_messages: :obj:`bool` @@ -599,7 +603,7 @@ class Chat(JsonDeserializable): permissions=None, slow_mode_delay=None, message_auto_delete_time=None, has_protected_content=None, sticker_set_name=None, can_set_sticker_set=None, linked_chat_id=None, location=None, - join_to_send_messages=None, join_by_request=None, **kwargs): + join_to_send_messages=None, join_by_request=None, has_restricted_voice_and_video_messages=None, **kwargs): self.id: int = id self.type: str = type self.title: str = title @@ -611,6 +615,7 @@ class Chat(JsonDeserializable): self.join_to_send_messages: bool = join_to_send_messages self.join_by_request: bool = join_by_request self.has_private_forwards: bool = has_private_forwards + self.has_restricted_voice_and_video_messages: bool = has_restricted_voice_and_video_messages self.description: str = description self.invite_link: str = invite_link self.pinned_message: Message = pinned_message @@ -1251,7 +1256,7 @@ class MessageEntity(Dictionaryable, JsonSerializable, JsonDeserializable): (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” (spoiler message), “code” (monowidth string), “pre” (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users - without usernames) + without usernames), “custom_emoji” (for inline custom emoji stickers) :type type: :obj:`str` :param offset: Offset in UTF-16 code units to the start of the entity @@ -1269,6 +1274,10 @@ class MessageEntity(Dictionaryable, JsonSerializable, JsonDeserializable): :param language: Optional. For “pre” only, the programming language of the entity text :type language: :obj:`str` + :param custom_emoji_id: Optional. For “custom_emoji” only, unique identifier of the custom emoji. + Use get_custom_emoji_stickers to get full information about the sticker. + :type custom_emoji_id: :obj:`str` + :return: Instance of the class :rtype: :class:`telebot.types.MessageEntity` """ @@ -1290,13 +1299,14 @@ class MessageEntity(Dictionaryable, JsonSerializable, JsonDeserializable): obj['user'] = User.de_json(obj['user']) return cls(**obj) - def __init__(self, type, offset, length, url=None, user=None, language=None, **kwargs): + def __init__(self, type, offset, length, url=None, user=None, language=None, custom_emoji_id=None, **kwargs): self.type: str = type self.offset: int = offset self.length: int = length self.url: str = url self.user: User = user self.language: str = language + self.custom_emoji_id: str = custom_emoji_id def to_json(self): return json.dumps(self.to_dict()) @@ -1307,7 +1317,8 @@ class MessageEntity(Dictionaryable, JsonSerializable, JsonDeserializable): "length": self.length, "url": self.url, "user": self.user, - "language": self.language} + "language": self.language, + "custom_emoji_id": self.custom_emoji_id} class Dice(JsonSerializable, Dictionaryable, JsonDeserializable): @@ -5439,13 +5450,17 @@ class StickerSet(JsonDeserializable): :param title: Sticker set title :type title: :obj:`str` + :param sticker_type: Type of stickers in the set, currently one of “regular”, “mask”, “custom_emoji” + :type sticker_type: :obj:`str` + :param is_animated: True, if the sticker set contains animated stickers :type is_animated: :obj:`bool` :param is_video: True, if the sticker set contains video stickers :type is_video: :obj:`bool` - :param contains_masks: True, if the sticker set contains masks + :param contains_masks: True, if the sticker set contains masks. Deprecated since Bot API 6.2, + use sticker_type instead. :type contains_masks: :obj:`bool` :param stickers: List of all set stickers @@ -5471,15 +5486,23 @@ class StickerSet(JsonDeserializable): obj['thumb'] = None return cls(**obj) - def __init__(self, name, title, is_animated, is_video, contains_masks, stickers, thumb=None, **kwargs): + def __init__(self, name, title, sticker_type, is_animated, is_video, stickers, thumb=None, **kwargs): self.name: str = name self.title: str = title + self.sticker_type: str = sticker_type self.is_animated: bool = is_animated self.is_video: bool = is_video - self.contains_masks: bool = contains_masks self.stickers: List[Sticker] = stickers self.thumb: PhotoSize = thumb + @property + def contains_masks(self): + """ + Deprecated since Bot API 6.2, use sticker_type instead. + """ + logger.warning("contains_masks is deprecated, use sticker_type instead") + return self.sticker_type == 'mask' + class Sticker(JsonDeserializable): """ @@ -5494,6 +5517,10 @@ class Sticker(JsonDeserializable): bots. Can't be used to download or reuse the file. :type file_unique_id: :obj:`str` + :param type: Type of the sticker, currently one of “regular”, “mask”, “custom_emoji”. The type of the sticker is + independent from its format, which is determined by the fields is_animated and is_video. + :type type: :obj:`str` + :param width: Sticker width :type width: :obj:`int` @@ -5521,6 +5548,9 @@ class Sticker(JsonDeserializable): :param mask_position: Optional. For mask stickers, the position where the mask should be placed :type mask_position: :class:`telebot.types.MaskPosition` + :param custom_emoji_id: Optional. For custom emoji stickers, unique identifier of the custom emoji + :type custom_emoji_id: :obj:`str` + :param file_size: Optional. File size in bytes :type file_size: :obj:`int` @@ -5542,11 +5572,12 @@ class Sticker(JsonDeserializable): obj['premium_animation'] = File.de_json(obj['premium_animation']) return cls(**obj) - def __init__(self, file_id, file_unique_id, width, height, is_animated, + def __init__(self, file_id, file_unique_id, type, width, height, is_animated, is_video, thumb=None, emoji=None, set_name=None, mask_position=None, file_size=None, - premium_animation=None, **kwargs): + premium_animation=None, custom_emoji_id=None, **kwargs): self.file_id: str = file_id self.file_unique_id: str = file_unique_id + self.type: str = type self.width: int = width self.height: int = height self.is_animated: bool = is_animated @@ -5557,6 +5588,7 @@ class Sticker(JsonDeserializable): self.mask_position: MaskPosition = mask_position self.file_size: int = file_size self.premium_animation: File = premium_animation + self.custom_emoji_id: int = custom_emoji_id diff --git a/tests/test_types.py b/tests/test_types.py index 29a5075..4587a16 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -83,7 +83,7 @@ def test_json_Message_Audio(): def test_json_Message_Sticker(): - json_string = r'{"message_id": 21552, "from": {"id": 590740002, "is_bot": false, "first_name": "⚜️ Ƥυrуα ⚜️", "username": "Purya", "language_code": "en"}, "chat": {"id": -1001309982000, "title": "123", "type": "supergroup"}, "date": 1594068909, "sticker": {"width": 368, "height": 368, "emoji": "🤖", "set_name": "ipuryapack", "is_animated": false, "is_video": true, "thumb": {"file_id": "AAMCBAADHQJOFL7mAAJUMF8Dj62hpmDhpRAYvkc8CtIqipolAAJ8AAPA-8cF9yxjgjkLS97A0D4iXQARtQAHbQADHy4AAhoE", "file_unique_id": "AQADwNA-Il0AAx8uAAI", "file_size": 7776, "width": 60, "height": 60}, "file_id": "CAACAgQAAx0CThS-5gACVDBfA4-toaZg4aUQGL5HWerSKoqaJQACArADwPvHBfcsY4I5C3feGgQ", "file_unique_id": "AgADfAADsPvHWQ", "file_size": 14602}}' + json_string = r'{"message_id": 21552, "from": {"id": 590740002, "is_bot": false, "first_name": "⚜️ Ƥυrуα ⚜️", "username": "Purya", "language_code": "en"}, "chat": {"id": -1001309982000, "title": "123", "type": "supergroup"}, "date": 1594068909, "sticker": {"type": "regular", "width": 368, "height": 368, "emoji": "🤖", "set_name": "ipuryapack", "is_animated": false, "is_video": true, "thumb": {"file_id": "AAMCBAADHQJOFL7mAAJUMF8Dj62hpmDhpRAYvkc8CtIqipolAAJ8AAPA-8cF9yxjgjkLS97A0D4iXQARtQAHbQADHy4AAhoE", "file_unique_id": "AQADwNA-Il0AAx8uAAI", "file_size": 7776, "width": 60, "height": 60}, "file_id": "CAACAgQAAx0CThS-5gACVDBfA4-toaZg4aUQGL5HWerSKoqaJQACArADwPvHBfcsY4I5C3feGgQ", "file_unique_id": "AgADfAADsPvHWQ", "file_size": 14602}}' msg = types.Message.de_json(json_string) assert msg.sticker.height == 368 assert msg.sticker.thumb.height == 60 @@ -91,7 +91,7 @@ def test_json_Message_Sticker(): def test_json_Message_Sticker_without_thumb(): - json_string = r'{"message_id": 21552, "from": {"id": 590740002, "is_bot": false, "first_name": "⚜️ Ƥυrуα ⚜️", "username": "Purya", "language_code": "en"}, "chat": {"id": -1001309982000, "title": "123", "type": "supergroup"}, "date": 1594068909, "sticker": {"width": 368, "height": 368, "emoji": "🤖", "set_name": "ipuryapack", "is_animated": false, "is_video": true, "file_id": "CAACAgQAAx0CThS-5gACVDBfA4-toaZg4aUQGL5HWerSKoqaJQACArADwPvHBfcsY4I5C3feGgQ", "file_unique_id": "AgADfAADsPvHWQ", "file_size": 14602}}' + json_string = r'{"message_id": 21552, "from": {"id": 590740002, "is_bot": false, "first_name": "⚜️ Ƥυrуα ⚜️", "username": "Purya", "language_code": "en"}, "chat": {"id": -1001309982000, "title": "123", "type": "supergroup"}, "date": 1594068909, "sticker": {"type": "regular", "width": 368, "height": 368, "emoji": "🤖", "set_name": "ipuryapack", "is_animated": false, "is_video": true, "file_id": "CAACAgQAAx0CThS-5gACVDBfA4-toaZg4aUQGL5HWerSKoqaJQACArADwPvHBfcsY4I5C3feGgQ", "file_unique_id": "AgADfAADsPvHWQ", "file_size": 14602}}' msg = types.Message.de_json(json_string) assert msg.sticker.height == 368 assert msg.sticker.thumb is None