diff --git a/README.md b/README.md index b8c4a6c..5eb412c 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.3! +##

Supported Bot API version: 6.4!

Official documentation

diff --git a/telebot/__init__.py b/telebot/__init__.py index 6a4dd7b..ef8ab71 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1763,7 +1763,8 @@ class TeleBot: allow_sending_without_reply: Optional[bool]=None, reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send photos. On success, the sent Message is returned. @@ -1808,6 +1809,9 @@ class TeleBot: :param message_thread_id: Identifier of a message thread, in which the message will be sent :type message_thread_id: :obj:`int` + + :param has_spoiler: Pass True, if the photo should be sent as a spoiler + :type has_spoiler: :obj:`bool` :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` @@ -1821,7 +1825,7 @@ class TeleBot: apihelper.send_photo( self.token, chat_id, photo, caption, reply_to_message_id, reply_markup, parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content, message_thread_id)) + allow_sending_without_reply, protect_content, message_thread_id, has_spoiler)) # TODO: Rewrite this method like in API. def send_audio( @@ -2171,7 +2175,8 @@ class TeleBot: reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, data: Optional[Union[Any, str]]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). @@ -2233,6 +2238,9 @@ class TeleBot: :param message_thread_id: Identifier of a message thread, in which the video will be sent :type message_thread_id: :obj:`int` + :param has_spoiler: Pass True, if the video should be sent as a spoiler + :type has_spoiler: :obj:`bool` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -2249,7 +2257,7 @@ class TeleBot: apihelper.send_video( self.token, chat_id, video, duration, caption, reply_to_message_id, reply_markup, parse_mode, supports_streaming, disable_notification, timeout, thumb, width, height, - caption_entities, allow_sending_without_reply, protect_content, message_thread_id)) + caption_entities, allow_sending_without_reply, protect_content, message_thread_id, has_spoiler)) def send_animation( self, chat_id: Union[int, str], animation: Union[Any, str], @@ -2266,7 +2274,8 @@ class TeleBot: allow_sending_without_reply: Optional[bool]=None, reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On success, the sent Message is returned. Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. @@ -2327,6 +2336,9 @@ class TeleBot: :param message_thread_id: Identifier of a message thread, in which the video will be sent :type message_thread_id: :obj:`int` + :param has_spoiler: Pass True, if the animation should be sent as a spoiler + :type has_spoiler: :obj:`bool` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -2339,7 +2351,7 @@ class TeleBot: apihelper.send_animation( self.token, chat_id, animation, duration, caption, reply_to_message_id, reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, protect_content, width, height, message_thread_id)) + caption_entities, allow_sending_without_reply, protect_content, width, height, message_thread_id, has_spoiler)) # TODO: Rewrite this method like in API. def send_video_note( @@ -2794,7 +2806,7 @@ class TeleBot: allow_sending_without_reply, protect_content, message_thread_id)) def send_chat_action( - self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None) -> bool: + self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None, message_thread_id: Optional[int]=None) -> bool: """ Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). @@ -2817,10 +2829,13 @@ class TeleBot: :param timeout: Timeout in seconds for the request. :type timeout: :obj:`int` + :param message_thread_id: The thread identifier of a message from which the reply will be sent(supergroups only) + :type message_thread_id: :obj:`int` + :return: Returns True on success. :rtype: :obj:`bool` """ - return apihelper.send_chat_action(self.token, chat_id, action, timeout) + return apihelper.send_chat_action(self.token, chat_id, action, timeout, message_thread_id) @util.deprecated(deprecation_text="Use ban_chat_member instead") def kick_chat_member( @@ -4636,8 +4651,8 @@ class TeleBot: def edit_forum_topic( self, chat_id: Union[int, str], - message_thread_id: int, name: str, - icon_custom_emoji_id: str, + message_thread_id: int, name: Optional[str]=None, + icon_custom_emoji_id: Optional[str]=None ) -> bool: """ Use this method to edit name and icon of a topic in a forum supergroup chat. The bot must be an @@ -4652,10 +4667,13 @@ class TeleBot: :param message_thread_id: Identifier of the topic to edit :type message_thread_id: :obj:`int` - :param name: New name of the topic, 1-128 characters + :param name: Optional, New name of the topic, 1-128 characters. If not specififed or empty, + the current name of the topic will be kept :type name: :obj:`str` - :param icon_custom_emoji_id: New custom emoji for the topic icon. Must be an emoji of type “tgs” and must be exactly 1 character long + :param icon_custom_emoji_id: Optional, New unique identifier of the custom emoji shown as the topic icon. + Use getForumTopicIconStickers to get all allowed custom emoji identifiers. Pass an empty string to remove the + icon. If not specified, the current icon will be kept :type icon_custom_emoji_id: :obj:`str` :return: On success, True is returned. @@ -4739,6 +4757,75 @@ class TeleBot: """ return apihelper.unpin_all_forum_topic_messages(self.token, chat_id, message_thread_id) + def edit_general_forum_topic(self, chat_id: Union[int, str], name: str) -> bool: + """ + Use this method to edit the name of the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#editgeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + + :param name: New topic name, 1-128 characters + :type name: :obj:`str` + """ + + return apihelper.edit_general_forum_topic(self.token, chat_id, name) + + def close_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to close the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#closegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return apihelper.close_general_forum_topic(self.token, chat_id) + + def reopen_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to reopen the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#reopengeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return apihelper.reopen_general_forum_topic(self.token, chat_id) + + def hide_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to hide the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#hidegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return apihelper.hide_general_forum_topic(self.token, chat_id) + + def unhide_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to unhide the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#unhidegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return apihelper.unhide_general_forum_topic(self.token, chat_id) + def get_forum_topic_icon_stickers(self) -> List[types.Sticker]: """ Use this method to get custom emoji stickers, which can be used as a forum topic icon by any user. diff --git a/telebot/apihelper.py b/telebot/apihelper.py index b25e036..1086f54 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -459,7 +459,7 @@ def send_photo( caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption_entities=None, allow_sending_without_reply=None, protect_content=None, - message_thread_id=None): + message_thread_id=None, has_spoiler=None): method_url = r'sendPhoto' payload = {'chat_id': chat_id} files = None @@ -489,6 +489,8 @@ def send_photo( payload['protect_content'] = protect_content if message_thread_id is not None: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return _make_request(token, method_url, params=payload, files=files, method='post') @@ -655,18 +657,20 @@ def send_contact( return _make_request(token, method_url, params=payload) -def send_chat_action(token, chat_id, action, timeout=None): +def send_chat_action(token, chat_id, action, timeout=None, message_thread_id=None): method_url = r'sendChatAction' payload = {'chat_id': chat_id, 'action': action} if timeout: payload['timeout'] = timeout + if message_thread_id is not None: + payload['message_thread_id'] = message_thread_id return _make_request(token, method_url, params=payload) def send_video(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None, thumb=None, width=None, height=None, caption_entities=None, allow_sending_without_reply=None, protect_content=None, - message_thread_id=None): + message_thread_id=None, has_spoiler=None): method_url = r'sendVideo' payload = {'chat_id': chat_id} files = None @@ -710,13 +714,16 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return _make_request(token, method_url, params=payload, files=files, method='post') def send_animation( token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None, caption_entities=None, - allow_sending_without_reply=None, protect_content=None, width=None, height=None, message_thread_id=None): + allow_sending_without_reply=None, protect_content=None, width=None, height=None, message_thread_id=None, + has_spoiler=None): method_url = r'sendAnimation' payload = {'chat_id': chat_id} files = None @@ -758,6 +765,8 @@ def send_animation( payload['height'] = height if message_thread_id: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return _make_request(token, method_url, params=payload, files=files, method='post') @@ -1766,9 +1775,13 @@ def create_forum_topic(token, chat_id, name, icon_color=None, icon_custom_emoji_ payload['icon_custom_emoji_id'] = icon_custom_emoji_id return _make_request(token, method_url, params=payload) -def edit_forum_topic(token, chat_id, message_thread_id, name, icon_custom_emoji_id): +def edit_forum_topic(token, chat_id, message_thread_id, name=None, icon_custom_emoji_id=None): method_url = r'editForumTopic' - payload = {'chat_id': chat_id, 'message_thread_id': message_thread_id, 'name': name, 'icon_custom_emoji_id': icon_custom_emoji_id} + payload = {'chat_id': chat_id, 'message_thread_id': message_thread_id} + if name is not None: + payload['name'] = name + if icon_custom_emoji_id is not None: + payload['icon_custom_emoji_id'] = icon_custom_emoji_id return _make_request(token, method_url, params=payload) def close_forum_topic(token, chat_id, message_thread_id): @@ -1802,6 +1815,31 @@ def stop_poll(token, chat_id, message_id, reply_markup=None): payload['reply_markup'] = _convert_markup(reply_markup) return _make_request(token, method_url, params=payload) +def edit_general_forum_topic(token, chat_id, name): + method_url = r'editGeneralForumTopic' + payload = {'chat_id': chat_id, 'name': name} + return _make_request(token, method_url, params=payload) + +def close_general_forum_topic(token, chat_id): + method_url = r'closeGeneralForumTopic' + payload = {'chat_id': chat_id} + return _make_request(token, method_url, params=payload) + +def reopen_general_forum_topic(token, chat_id): + method_url = r'reopenGeneralForumTopic' + payload = {'chat_id': chat_id} + return _make_request(token, method_url, params=payload) + +def hide_general_forum_topic(token, chat_id): + method_url = r'hideGeneralForumTopic' + payload = {'chat_id': chat_id} + return _make_request(token, method_url, params=payload) + +def unhide_general_forum_topic(token, chat_id): + method_url = r'unhideGeneralForumTopic' + payload = {'chat_id': chat_id} + return _make_request(token, method_url, params=payload) + def _convert_list_json_serializable(results): ret = '' diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index e8249fc..6e3ec9a 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -2627,7 +2627,8 @@ class AsyncTeleBot: allow_sending_without_reply: Optional[bool]=None, reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send photos. On success, the sent Message is returned. @@ -2672,6 +2673,9 @@ class AsyncTeleBot: :param message_thread_id: Identifier of a message thread, in which the message will be sent :type message_thread_id: :obj:`int` + + :param has_spoiler: Pass True, if the photo should be sent as a spoiler + :type has_spoiler: :obj:`bool` :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` @@ -2685,7 +2689,7 @@ class AsyncTeleBot: await asyncio_helper.send_photo( self.token, chat_id, photo, caption, reply_to_message_id, reply_markup, parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content, message_thread_id)) + allow_sending_without_reply, protect_content, message_thread_id, has_spoiler)) async def send_audio( self, chat_id: Union[int, str], audio: Union[Any, str], @@ -3031,7 +3035,8 @@ class AsyncTeleBot: reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, data: Optional[Union[Any, str]]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). @@ -3093,6 +3098,9 @@ class AsyncTeleBot: :param message_thread_id: Identifier of a message thread, in which the video will be sent :type message_thread_id: :obj:`int` + :param has_spoiler: Pass True, if the video should be sent as a spoiler + :type has_spoiler: :obj:`bool` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3110,7 +3118,7 @@ class AsyncTeleBot: await asyncio_helper.send_video( self.token, chat_id, video, duration, caption, reply_to_message_id, reply_markup, parse_mode, supports_streaming, disable_notification, timeout, thumb, width, height, - caption_entities, allow_sending_without_reply, protect_content, message_thread_id)) + caption_entities, allow_sending_without_reply, protect_content, message_thread_id, has_spoiler)) async def send_animation( self, chat_id: Union[int, str], animation: Union[Any, str], @@ -3127,7 +3135,8 @@ class AsyncTeleBot: allow_sending_without_reply: Optional[bool]=None, reply_markup: Optional[REPLY_MARKUP_TYPES]=None, timeout: Optional[int]=None, - message_thread_id: Optional[int]=None) -> types.Message: + message_thread_id: Optional[int]=None, + has_spoiler: Optional[bool]=None) -> types.Message: """ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On success, the sent Message is returned. Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. @@ -3188,6 +3197,9 @@ class AsyncTeleBot: :param message_thread_id: Identifier of a message thread, in which the video will be sent :type message_thread_id: :obj:`int` + :param has_spoiler: Pass True, if the animation should be sent as a spoiler + :type has_spoiler: :obj:`bool` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3200,7 +3212,7 @@ class AsyncTeleBot: await asyncio_helper.send_animation( self.token, chat_id, animation, duration, caption, reply_to_message_id, reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, width, height, protect_content, message_thread_id)) + caption_entities, allow_sending_without_reply, width, height, protect_content, message_thread_id, has_spoiler)) async def send_video_note( self, chat_id: Union[int, str], data: Union[Any, str], @@ -3652,7 +3664,7 @@ class AsyncTeleBot: ) async def send_chat_action( - self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None) -> bool: + self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None, message_thread_id: Optional[int]=None) -> bool: """ Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). @@ -3675,10 +3687,13 @@ class AsyncTeleBot: :param timeout: Timeout in seconds for the request. :type timeout: :obj:`int` + :param message_thread_id: The thread to which the message will be sent(supergroups only) + :type message_thread_id: :obj:`int` + :return: Returns True on success. :rtype: :obj:`bool` """ - return await asyncio_helper.send_chat_action(self.token, chat_id, action, timeout) + return await asyncio_helper.send_chat_action(self.token, chat_id, action, timeout, message_thread_id) async def kick_chat_member( self, chat_id: Union[int, str], user_id: int, @@ -5502,8 +5517,8 @@ class AsyncTeleBot: async def edit_forum_topic( self, chat_id: Union[int, str], - message_thread_id: int, name: str, - icon_custom_emoji_id: str, + message_thread_id: int, name: Optional[str]=None, + icon_custom_emoji_id: Optional[str]=None ) -> bool: """ Use this method to edit name and icon of a topic in a forum supergroup chat. The bot must be an @@ -5518,10 +5533,13 @@ class AsyncTeleBot: :param message_thread_id: Identifier of the topic to edit :type message_thread_id: :obj:`int` - :param name: New name of the topic, 1-128 characters + :param name: Optional, New name of the topic, 1-128 characters. If not specififed or empty, + the current name of the topic will be kept :type name: :obj:`str` - :param icon_custom_emoji_id: New custom emoji for the topic icon. Must be an emoji of type “tgs” and must be exactly 1 character long + :param icon_custom_emoji_id: Optional, New unique identifier of the custom emoji shown as the topic icon. + Use getForumTopicIconStickers to get all allowed custom emoji identifiers. Pass an empty string to remove the + icon. If not specified, the current icon will be kept :type icon_custom_emoji_id: :obj:`str` :return: On success, True is returned. @@ -5605,6 +5623,75 @@ class AsyncTeleBot: """ return await asyncio_helper.unpin_all_forum_topic_messages(self.token, chat_id, message_thread_id) + async def edit_general_forum_topic(self, chat_id: Union[int, str], name: str) -> bool: + """ + Use this method to edit the name of the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#editgeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + + :param name: New topic name, 1-128 characters + :type name: :obj:`str` + """ + + return await asyncio_helper.edit_general_forum_topic(self.token, chat_id, name) + + async def close_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to close the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#closegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return await asyncio_helper.close_general_forum_topic(self.token, chat_id) + + async def reopen_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to reopen the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#reopengeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return await asyncio_helper.reopen_general_forum_topic(self.token, chat_id) + + async def hide_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to hide the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#hidegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return await asyncio_helper.hide_general_forum_topic(self.token, chat_id) + + async def unhide_general_forum_topic(self, chat_id: Union[int, str]) -> bool: + """ + Use this method to unhide the 'General' topic in a forum supergroup chat. + The bot must be an administrator in the chat for this to work and must have can_manage_topics administrator rights. + Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#unhidegeneralforumtopic + + :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :type chat_id: :obj:`int` or :obj:`str` + """ + return await asyncio_helper.unhide_general_forum_topic(self.token, chat_id) + async def get_forum_topic_icon_stickers(self) -> List[types.Sticker]: """ Use this method to get custom emoji stickers, which can be used as a forum topic icon by any user. diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 9f5b59a..4371885 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -453,7 +453,7 @@ async def send_photo( caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption_entities=None, allow_sending_without_reply=None, protect_content=None, - message_thread_id=None): + message_thread_id=None, has_spoiler=None): method_url = r'sendPhoto' payload = {'chat_id': chat_id} files = None @@ -483,6 +483,8 @@ async def send_photo( payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return await _process_request(token, method_url, params=payload, files=files, method='post') @@ -647,18 +649,20 @@ async def send_contact( return await _process_request(token, method_url, params=payload) -async def send_chat_action(token, chat_id, action, timeout=None): +async def send_chat_action(token, chat_id, action, timeout=None, message_thread_id=None): method_url = r'sendChatAction' payload = {'chat_id': chat_id, 'action': action} if timeout: payload['timeout'] = timeout + if message_thread_id: + payload['message_thread_id'] = message_thread_id return await _process_request(token, method_url, params=payload) async def send_video(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None, thumb=None, width=None, height=None, caption_entities=None, allow_sending_without_reply=None, - protect_content=None, message_thread_id=None): + protect_content=None, message_thread_id=None, has_spoiler=None): method_url = r'sendVideo' payload = {'chat_id': chat_id} files = None @@ -702,13 +706,16 @@ async def send_video(token, chat_id, data, duration=None, caption=None, reply_to payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return await _process_request(token, method_url, params=payload, files=files, method='post') async def send_animation( token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None, caption_entities=None, - allow_sending_without_reply=None, width=None, height=None, protect_content=None, message_thread_id=None): + allow_sending_without_reply=None, width=None, height=None, protect_content=None, message_thread_id=None, + has_spoiler=None): method_url = r'sendAnimation' payload = {'chat_id': chat_id} files = None @@ -750,6 +757,8 @@ async def send_animation( payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if has_spoiler is not None: + payload['has_spoiler'] = has_spoiler return await _process_request(token, method_url, params=payload, files=files, method='post') @@ -1757,9 +1766,13 @@ async def create_forum_topic(token, chat_id, name, icon_color=None, icon_custom_ payload['icon_custom_emoji_id'] = icon_custom_emoji_id return await _process_request(token, method_url, params=payload) -async def edit_forum_topic(token, chat_id, message_thread_id, name, icon_custom_emoji_id): +async def edit_forum_topic(token, chat_id, message_thread_id, name=None, icon_custom_emoji_id=None): method_url = r'editForumTopic' - payload = {'chat_id': chat_id, 'message_thread_id': message_thread_id, 'name': name, 'icon_custom_emoji_id': icon_custom_emoji_id} + payload = {'chat_id': chat_id, 'message_thread_id': message_thread_id} + if name is not None: + payload['name'] = name + if icon_custom_emoji_id is not None: + payload['icon_custom_emoji_id'] = icon_custom_emoji_id return await _process_request(token, method_url, params=payload) async def close_forum_topic(token, chat_id, message_thread_id): @@ -1786,6 +1799,31 @@ async def get_forum_topic_icon_stickers(token): method_url = r'getForumTopicIconStickers' return await _process_request(token, method_url) +async def edit_general_forum_topic(token, chat_id, name): + method_url = r'editGeneralForumTopic' + payload = {'chat_id': chat_id, 'name': name} + return await _process_request(token, method_url, params=payload) + +async def close_general_forum_topic(token, chat_id): + method_url = r'closeGeneralForumTopic' + payload = {'chat_id': chat_id} + return await _process_request(token, method_url, params=payload) + +async def reopen_general_forum_topic(token, chat_id): + method_url = r'reopenGeneralForumTopic' + payload = {'chat_id': chat_id} + return await _process_request(token, method_url, params=payload) + +async def hide_general_forum_topic(token, chat_id): + method_url = r'hideGeneralForumTopic' + payload = {'chat_id': chat_id} + return await _process_request(token, method_url, params=payload) + +async def unhide_general_forum_topic(token, chat_id): + method_url = r'unhideGeneralForumTopic' + payload = {'chat_id': chat_id} + return await _process_request(token, method_url, params=payload) + async def _convert_list_json_serializable(results): ret = '' for r in results: diff --git a/telebot/types.py b/telebot/types.py index 5e843d2..2938674 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -570,6 +570,14 @@ class Chat(JsonDeserializable): automatically deleted; in seconds. Returned only in getChat. :type message_auto_delete_time: :obj:`int` + :param has_aggressive_anti_spam_enabled: Optional. :obj:`bool`, if the chat has enabled aggressive anti-spam + protection. Returned only in getChat. + :type has_aggressive_anti_spam_enabled: :obj:`bool` + + :param has_hidden_members: Optional. :obj:`bool`, if the chat has enabled hidden members. Returned only in + getChat. + :type has_hidden_members: :obj:`bool` + :param has_protected_content: Optional. :obj:`bool`, if messages from the chat can't be forwarded to other chats. Returned only in getChat. :type has_protected_content: :obj:`bool` @@ -615,7 +623,8 @@ class Chat(JsonDeserializable): 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, has_restricted_voice_and_video_messages=None, - is_forum=None, active_usernames=None, emoji_status_custom_emoji_id=None, **kwargs): + is_forum=None, active_usernames=None, emoji_status_custom_emoji_id=None, + has_hidden_members=None, has_aggressive_anti_spam_enabled=None, **kwargs): self.id: int = id self.type: str = type self.title: str = title @@ -642,6 +651,8 @@ class Chat(JsonDeserializable): self.location: ChatLocation = location self.active_usernames: List[str] = active_usernames self.emoji_status_custom_emoji_id: str = emoji_status_custom_emoji_id + self.has_hidden_members: bool = has_hidden_members + self.has_aggressive_anti_spam_enabled: bool = has_aggressive_anti_spam_enabled class MessageID(JsonDeserializable): @@ -812,6 +823,9 @@ class Message(JsonDeserializable): commands, etc. that appear in the caption :type caption_entities: :obj:`list` of :class:`telebot.types.MessageEntity` + :param has_media_spoiler: Optional. True, if the message media is covered by a spoiler animation + :type has_media_spoiler: :obj:`bool` + :param contact: Optional. Message is a shared contact, information about the contact :type contact: :class:`telebot.types.Contact` @@ -892,6 +906,10 @@ class Message(JsonDeserializable): Telegram Login » :type connected_website: :obj:`str` + :param write_access_allowed: Optional. Service message: the user allowed the bot added to the attachment + menu to write messages + :type write_access_allowed: :class:`telebot.types.WriteAccessAllowed` + :param passport_data: Optional. Telegram Passport data :type passport_data: :class:`telebot.types.PassportData` @@ -902,12 +920,21 @@ class Message(JsonDeserializable): :param forum_topic_created: Optional. Service message: forum topic created :type forum_topic_created: :class:`telebot.types.ForumTopicCreated` + :param forum_topic_edited: Optional. Service message: forum topic edited + :type forum_topic_edited: :class:`telebot.types.ForumTopicEdited` + :param forum_topic_closed: Optional. Service message: forum topic closed :type forum_topic_closed: :class:`telebot.types.ForumTopicClosed` :param forum_topic_reopened: Optional. Service message: forum topic reopened :type forum_topic_reopened: :class:`telebot.types.ForumTopicReopened` + :param general_forum_topic_hidden: Optional. Service message: the 'General' forum topic hidden + :type general_forum_topic_hidden: :class:`telebot.types.GeneralForumTopicHidden` + + :param general_forum_topic_unhidden: Optional. Service message: the 'General' forum topic unhidden + :type general_forum_topic_unhidden: :class:`telebot.types.GeneralForumTopicUnhidden` + :param video_chat_scheduled: Optional. Service message: video chat scheduled :type video_chat_scheduled: :class:`telebot.types.VideoChatScheduled` @@ -1110,6 +1137,20 @@ class Message(JsonDeserializable): if 'forum_topic_reopened' in obj: opts['forum_topic_reopened'] = ForumTopicReopened.de_json(obj['forum_topic_reopened']) content_type = 'forum_topic_reopened' + if 'has_media_spoiler' in obj: + opts['has_media_spoiler'] = obj['has_media_spoiler'] + if 'forum_topic_edited' in obj: + opts['forum_topic_edited'] = ForumTopicEdited.de_json(obj['forum_topic_edited']) + content_type = 'forum_topic_edited' + if 'general_forum_topic_hidden' in obj: + opts['general_forum_topic_hidden'] = GeneralForumTopicHidden.de_json(obj['general_forum_topic_hidden']) + content_type = 'general_forum_topic_hidden' + if 'general_forum_topic_unhidden' in obj: + opts['general_forum_topic_unhidden'] = GeneralForumTopicUnhidden.de_json(obj['general_forum_topic_unhidden']) + content_type = 'general_forum_topic_unhidden' + if 'write_access_allowed' in obj: + opts['write_access_allowed'] = WriteAccessAllowed.de_json(obj['write_access_allowed']) + content_type = 'write_access_allowed' return cls(message_id, from_user, date, chat, content_type, opts, json_string) @classmethod @@ -1200,6 +1241,11 @@ class Message(JsonDeserializable): self.forum_topic_created: Optional[ForumTopicCreated] = None self.forum_topic_closed: Optional[ForumTopicClosed] = None self.forum_topic_reopened: Optional[ForumTopicReopened] = None + self.has_media_spoiler: Optional[bool] = None + self.forum_topic_edited: Optional[ForumTopicEdited] = None + self.general_forum_topic_hidden: Optional[GeneralForumTopicHidden] = None + self.general_forum_topic_unhidden: Optional[GeneralForumTopicUnhidden] = None + self.write_access_allowed: Optional[WriteAccessAllowed] = None for key in options: setattr(self, key, options[key]) self.json = json_string @@ -2055,13 +2101,21 @@ class ReplyKeyboardMarkup(JsonSerializable): replies to the request with a keyboard to select the new language. Other users in the group don't see the keyboard. :type selective: :obj:`bool` + :param is_persistent: Optional. Use this parameter if you want to show the keyboard to specific users only. + Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's message is a + reply (has reply_to_message_id), sender of the original message. + + Example: A user requests to change the bot's language, bot replies to the request with a keyboard to + select the new language. Other users in the group don't see the keyboard. + :return: Instance of the class :rtype: :class:`telebot.types.ReplyKeyboardMarkup` """ max_row_keys = 12 def __init__(self, resize_keyboard: Optional[bool]=None, one_time_keyboard: Optional[bool]=None, - selective: Optional[bool]=None, row_width: int=3, input_field_placeholder: Optional[str]=None): + selective: Optional[bool]=None, row_width: int=3, input_field_placeholder: Optional[str]=None, + is_persistent: Optional[bool]=None): if row_width > self.max_row_keys: # Todo: Will be replaced with Exception in future releases if not DISABLE_KEYLEN_ERROR: @@ -2074,6 +2128,7 @@ class ReplyKeyboardMarkup(JsonSerializable): self.row_width: int = row_width self.input_field_placeholder: str = input_field_placeholder self.keyboard: List[List[KeyboardButton]] = [] + self.is_persistent: bool = is_persistent def add(self, *args, row_width=None): """ @@ -2139,6 +2194,8 @@ class ReplyKeyboardMarkup(JsonSerializable): json_dict['selective'] = self.selective if self.input_field_placeholder: json_dict['input_field_placeholder'] = self.input_field_placeholder + if self.is_persistent is not None: + json_dict['is_persistent'] = self.is_persistent return json.dumps(json_dict) @@ -5781,18 +5838,26 @@ class InputMediaPhoto(InputMedia): instead of parse_mode :type caption_entities: :obj:`list` of :class:`telebot.types.MessageEntity` + :param has_spoiler: Optional. True, if the uploaded photo is a spoiler + :type has_spoiler: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.InputMediaPhoto` """ - def __init__(self, media, caption=None, parse_mode=None, caption_entities=None): + def __init__(self, media, caption=None, parse_mode=None, caption_entities=None, has_spoiler=None): if util.is_pil_image(media): media = util.pil_image_to_file(media) super(InputMediaPhoto, self).__init__( type="photo", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities) + self.has_spoiler: Optional[bool] = has_spoiler + def to_dict(self): - return super(InputMediaPhoto, self).to_dict() + ret = super(InputMediaPhoto, self).to_dict() + if self.has_spoiler is not None: + ret['has_spoiler'] = self.has_spoiler + return ret class InputMediaVideo(InputMedia): @@ -5836,11 +5901,14 @@ class InputMediaVideo(InputMedia): :param supports_streaming: Optional. Pass True, if the uploaded video is suitable for streaming :type supports_streaming: :obj:`bool` + :param has_spoiler: Optional. True, if the uploaded video is a spoiler + :type has_spoiler: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.InputMediaVideo` """ def __init__(self, media, thumb=None, caption=None, parse_mode=None, caption_entities=None, - width=None, height=None, duration=None, supports_streaming=None): + width=None, height=None, duration=None, supports_streaming=None, has_spoiler=None): super(InputMediaVideo, self).__init__( type="video", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities) self.thumb = thumb @@ -5848,6 +5916,7 @@ class InputMediaVideo(InputMedia): self.height = height self.duration = duration self.supports_streaming = supports_streaming + self.has_spoiler: Optional[bool] = has_spoiler def to_dict(self): ret = super(InputMediaVideo, self).to_dict() @@ -5861,6 +5930,8 @@ class InputMediaVideo(InputMedia): ret['duration'] = self.duration if self.supports_streaming: ret['supports_streaming'] = self.supports_streaming + if self.has_spoiler is not None: + ret['has_spoiler'] = self.has_spoiler return ret @@ -5902,17 +5973,21 @@ class InputMediaAnimation(InputMedia): :param duration: Optional. Animation duration in seconds :type duration: :obj:`int` + :param has_spoiler: Optional. True, if the uploaded animation is a spoiler + :type has_spoiler: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.InputMediaAnimation` """ def __init__(self, media, thumb=None, caption=None, parse_mode=None, caption_entities=None, - width=None, height=None, duration=None): + width=None, height=None, duration=None, has_spoiler=None): super(InputMediaAnimation, self).__init__( type="animation", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities) self.thumb = thumb self.width = width self.height = height self.duration = duration + self.has_spoiler: Optional[bool] = has_spoiler def to_dict(self): ret = super(InputMediaAnimation, self).to_dict() @@ -5924,6 +5999,8 @@ class InputMediaAnimation(InputMedia): ret['height'] = self.height if self.duration: ret['duration'] = self.duration + if self.has_spoiler is not None: + ret['has_spoiler'] = self.has_spoiler return ret @@ -6829,6 +6906,61 @@ class ForumTopicReopened(JsonDeserializable): def __init__(self) -> None: pass +class ForumTopicEdited(JsonDeserializable): + """ + This object represents a service message about an edited forum topic. + + Telegram documentation: https://core.telegram.org/bots/api#forumtopicedited + + :param name: Optional, Name of the topic(if updated) + :type name: :obj:`str` + + :param icon_custom_emoji_id: Optional. New identifier of the custom emoji shown as the topic icon, if it was edited; + an empty string if the icon was removed + :type icon_custom_emoji_id: :obj:`str` + """ + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + return cls(**obj) + + def __init__(self, name: Optional[str]=None, icon_custom_emoji_id: Optional[str]=None) -> None: + self.name: Optional[str] = name + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id + + +class GeneralForumTopicHidden(JsonDeserializable): + """ + This object represents a service message about General forum topic hidden in the chat. + Currently holds no information. + + Telegram documentation: https://core.telegram.org/bots/api#generalforumtopichidden + """ + @classmethod + def de_json(cls, json_string): + return cls() + + def __init__(self) -> None: + pass + + +class GeneralForumTopicUnhidden(JsonDeserializable): + """ + This object represents a service message about General forum topic unhidden in the chat. + Currently holds no information. + + Telegram documentation: https://core.telegram.org/bots/api#generalforumtopicunhidden + """ + + @classmethod + def de_json(cls, json_string): + return cls() + + def __init__(self) -> None: + pass + + class ForumTopic(JsonDeserializable): """ @@ -6865,6 +6997,19 @@ class ForumTopic(JsonDeserializable): self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id +class WriteAccessAllowed(JsonDeserializable): + """ + This object represents a service message about a user allowed to post messages in the chat. + Currently holds no information. + + Telegram documentation: https://core.telegram.org/bots/api#writeaccessallowed + """ + @classmethod + def de_json(cls, json_string): + return cls() + + def __init__(self) -> None: + pass