diff --git a/telebot/__init__.py b/telebot/__init__.py index 11b402f9a..a9ca9314d 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -251,6 +251,7 @@ def __init__( self.purchased_paid_media_handlers = [] self.managed_bot_handlers = [] self.guest_message_handlers = [] + self.subscription_handlers = [] self.custom_filters = {} self.state_handlers = [] @@ -730,6 +731,7 @@ def process_new_updates(self, updates: List[types.Update]): new_purchased_paid_media = None new_managed_bots = None new_guest_messages = None + new_subscriptions = None for update in updates: if apihelper.ENABLE_MIDDLEWARE and not self.use_class_middlewares: @@ -820,6 +822,9 @@ def process_new_updates(self, updates: List[types.Update]): if update.guest_message: if new_guest_messages is None: new_guest_messages = [] new_guest_messages.append(update.guest_message) + if update.subscription: + if new_subscriptions is None: new_subscriptions = [] + new_subscriptions.append(update.subscription) if new_messages: self.process_new_messages(new_messages) @@ -847,6 +852,8 @@ def process_new_updates(self, updates: List[types.Update]): self.process_new_my_chat_member(new_my_chat_members) if new_chat_members: self.process_new_chat_member(new_chat_members) + if new_subscriptions: + self.process_new_subscription(new_subscriptions) if new_chat_join_request: self.process_new_chat_join_request(new_chat_join_request) if new_message_reactions: @@ -1025,6 +1032,12 @@ def process_new_guest_message(self, new_guest_messages): """ self._notify_command_handlers(self.guest_message_handlers, new_guest_messages, 'guest_message') + def process_new_subscription(self, new_subscriptions): + """ + :meta private: + """ + self._notify_command_handlers(self.subscription_handlers, new_subscriptions, 'subscription') + def process_middlewares(self, update): """ :meta private: @@ -1766,7 +1779,9 @@ def send_message( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send text messages. @@ -1833,6 +1848,14 @@ def send_message( required if the message is sent to a direct messages chat :type direct_messages_topic_id: :obj:`int` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :param suggested_post_parameters: A JSON-serialized object containing the parameters of the suggested post to send; for direct messages chats only. If the message is sent as a reply to another suggested post, then that suggested post is automatically declined. @@ -1887,7 +1910,7 @@ def send_message( timeout=timeout, entities=entities, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, link_preview_options=link_preview_options, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters + suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -2537,7 +2560,9 @@ def send_photo( show_caption_above_media: Optional[bool]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send photos. On success, the sent Message is returned. @@ -2611,6 +2636,14 @@ def send_photo( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -2644,7 +2677,8 @@ def send_photo( message_thread_id=message_thread_id, has_spoiler=has_spoiler, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -2760,7 +2794,9 @@ def send_audio( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .MP3 or .M4A format. On success, the sent Message is returned. Bots can currently send audio files of up to 50 MB in size, @@ -2849,6 +2885,14 @@ def send_audio( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -2885,7 +2929,8 @@ def send_audio( timeout=timeout, thumbnail=thumbnail, caption_entities=caption_entities, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -2906,7 +2951,9 @@ def send_voice( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS, or in .MP3 format, or in .M4A format (other formats may be sent as Audio or Document). On success, the sent Message is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. @@ -2976,6 +3023,14 @@ def send_voice( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. """ parse_mode = self.parse_mode if (parse_mode is None) else parse_mode @@ -3007,7 +3062,7 @@ def send_voice( caption_entities=caption_entities, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters + suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -3033,7 +3088,9 @@ def send_document( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send general files. @@ -3115,6 +3172,14 @@ def send_document( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3160,7 +3225,8 @@ def send_document( disable_content_type_detection=disable_content_type_detection, visible_file_name=visible_file_name, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -3181,7 +3247,9 @@ def send_sticker( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers. On success, the sent Message is returned. @@ -3246,6 +3314,14 @@ def send_sticker( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3281,7 +3357,9 @@ def send_sticker( protect_content=protect_content, message_thread_id=message_thread_id, emoji=emoji, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id + ) ) @@ -3313,7 +3391,9 @@ def send_video( cover: Optional[Union[Any, str]]=None, start_timestamp: Optional[int]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). @@ -3416,6 +3496,14 @@ def send_video( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3459,7 +3547,7 @@ def send_video( reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, cover=cover, start_timestamp=start_timestamp, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters) + suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -3487,7 +3575,9 @@ def send_animation( show_caption_above_media: Optional[bool]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=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. @@ -3579,6 +3669,14 @@ def send_animation( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3616,7 +3714,8 @@ def send_animation( width=width, height=height, message_thread_id=message_thread_id, reply_parameters=reply_parameters, has_spoiler=has_spoiler, business_connection_id=business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -3638,7 +3737,9 @@ def send_video_note( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ As of v.4.0, Telegram clients support rounded square MPEG4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent Message is returned. @@ -3712,6 +3813,14 @@ def send_video_note( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3746,7 +3855,8 @@ def send_video_note( disable_notification=disable_notification, timeout=timeout, thumbnail=thumbnail, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) def send_paid_media( @@ -3951,7 +4061,9 @@ def send_location( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send point on the map. On success, the sent Message is returned. @@ -4023,6 +4135,14 @@ def send_location( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4054,7 +4174,7 @@ def send_location( heading=heading, proximity_alert_radius=proximity_alert_radius, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters) + suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -4188,7 +4308,9 @@ def send_venue( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send information about a venue. On success, the sent Message is returned. @@ -4267,6 +4389,14 @@ def send_venue( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4298,7 +4428,7 @@ def send_venue( timeout=timeout, google_place_id=google_place_id, google_place_type=google_place_type, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters) + suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -4317,7 +4447,9 @@ def send_contact( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send phone contacts. On success, the sent Message is returned. @@ -4383,6 +4515,14 @@ def send_contact( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4413,7 +4553,7 @@ def send_contact( disable_notification=disable_notification, reply_markup=reply_markup, timeout=timeout, protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, receiver_user_id=receiver_user_id, callback_query_id=callback_query_id) ) @@ -6707,6 +6847,176 @@ def stop_poll( return types.Poll.de_json( apihelper.stop_poll(self.token, chat_id, message_id, reply_markup=reply_markup, business_connection_id=business_connection_id) ) + + def delete_ephemeral_message( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int) -> bool: + """ + Use this method to delete an ephemeral message. Note that it is not guaranteed that the + user will receive the message deletion event, especially if they are offline. Returns True on success + + Telegram documentation: https://core.telegram.org/bots/api#deleteephemeralmessage + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to delete + :type ephemeral_message_id: :obj:`int` + + :return: Returns True on success. + :rtype: :obj:`bool` + """ + return apihelper.delete_ephemeral_message( + self.token, chat_id, receiver_user_id, ephemeral_message_id) + + + def edit_ephemeral_message_text( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, text: str, + parse_mode: Optional[str]=None, entities: Optional[List[types.MessageEntity]]=None, + link_preview_options: Optional[types.LinkPreviewOptions]=None, reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit an ephemeral text message. Note that it is not guaranteed that the + user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagetext + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param text: New text of the message, 1-4096 characters after entity parsing + :type text: :obj:`str` + + :param parse_mode: Mode for parsing entities in the message text. See formatting options for more details. + :type parse_mode: :obj:`str` + + :param entities: A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode + :type entities: :obj:`list` of :obj:`MessageEntity` + + :param link_preview_options: Link preview generation options for the message + :type link_preview_options: :obj:`LinkPreviewOptions` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + parse_mode = self.parse_mode if (parse_mode is None) else parse_mode + + return apihelper.edit_ephemeral_message_text( + self.token, chat_id, receiver_user_id, ephemeral_message_id, text, + parse_mode=parse_mode, entities=entities, + link_preview_options=link_preview_options, reply_markup=reply_markup) + + def edit_ephemeral_message_media( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, media: types.InputMedia, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit the media of an ephemeral message. Note that it is not guaranteed + that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagemedia + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param media: A JSON-serialized object for the new media content of the message. A new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. + :type media: :obj:`InputMedia` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + return apihelper.edit_ephemeral_message_media( + self.token, chat_id, receiver_user_id, ephemeral_message_id, media, + reply_markup=reply_markup) + + + def edit_ephemeral_message_caption( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, caption: Optional[str]=None, + parse_mode: Optional[str]=None, + caption_entities: Optional[List[types.MessageEntity]]=None, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit the caption of an ephemeral message. Note that it is not guaranteed + that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagecaption + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param caption: New caption of the message, 0-1024 characters after entities parsing + :type caption: :obj:`str` + + :param parse_mode: Mode for parsing entities in the message caption. See formatting options for more details. + :type parse_mode: :obj:`str` + + :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode + :type caption_entities: :obj:`list` of :obj:`MessageEntity` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + parse_mode = self.parse_mode if (parse_mode is None) else parse_mode + + return apihelper.edit_ephemeral_message_caption( + self.token, chat_id, receiver_user_id, ephemeral_message_id, caption, + parse_mode=parse_mode, caption_entities=caption_entities, + reply_markup=reply_markup) + + + def edit_ephemeral_message_reply_markup( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit only the reply markup of an ephemeral message. Note that it is + not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagereplymarkup + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + return apihelper.edit_ephemeral_message_reply_markup( + self.token, chat_id, receiver_user_id, ephemeral_message_id, reply_markup=reply_markup) def answer_shipping_query( @@ -10941,6 +11251,54 @@ def register_guest_message_handler(self, callback: Callable, func: Optional[Call handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) self.add_guest_message_handler(handler_dict) + def subscription_handler(self, func=None, **kwargs): + """ + User payment subscription has changed. + + :param func: Function executed as a filter + :type func: :obj:`function` + + :param kwargs: Optional keyword arguments(custom filters) + :return: None + """ + def decorator(handler): + handler_dict = self._build_handler_dict(handler, func=func, **kwargs) + self.add_subscription_handler(handler_dict) + return handler + + return decorator + + def add_subscription_handler(self, handler_dict): + """ + Adds a subscription handler. + Note that you should use register_subscription_handler to add subscription_handler to the bot. + + :meta private: + + :param handler_dict: + :return: + """ + self.subscription_handlers.append(handler_dict) + + def register_subscription_handler(self, callback: Callable, func: Optional[Callable]=None, pass_bot: Optional[bool]=False, **kwargs): + """ + Registers subscription handler. + + :param callback: function to be called + :type callback: :obj:`function` + + :param func: Function executed as a filter + :type func: :obj:`function` + + :param pass_bot: True if you need to pass TeleBot instance to handler(useful for separating handlers into different files) + :type pass_bot: :obj:`bool` + + :param kwargs: Optional keyword arguments(custom filters) + :return: None + """ + handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) + self.add_subscription_handler(handler_dict) + def add_custom_filter(self, custom_filter: Union[SimpleCustomFilter, AdvancedCustomFilter]): """ diff --git a/telebot/apihelper.py b/telebot/apihelper.py index afb4c7e42..39a40efb7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -248,7 +248,8 @@ def send_message( entities=None, protect_content=None, message_thread_id=None, reply_parameters=None, link_preview_options=None, business_connection_id=None, message_effect_id=None, allow_paid_broadcast=None, - direct_messages_topic_id=None, suggested_post_parameters=None): + direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendMessage' payload = {'chat_id': str(chat_id), 'text': text} if link_preview_options is not None: @@ -279,6 +280,10 @@ def send_message( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, method='post') @@ -617,7 +622,7 @@ def send_photo( caption_entities=None, protect_content=None, message_thread_id=None, has_spoiler=None, reply_parameters=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, allow_paid_broadcast=None, - direct_messages_topic_id=None, suggested_post_parameters=None): + direct_messages_topic_id=None, suggested_post_parameters=None,receiver_user_id=None, callback_query_id=None): method_url = r'sendPhoto' payload = {'chat_id': chat_id} files = None @@ -659,6 +664,10 @@ def send_photo( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, files=files, method='post') def send_live_photo( @@ -793,7 +802,7 @@ def send_location( proximity_alert_radius=None, protect_content=None, message_thread_id=None, reply_parameters=None, business_connection_id=None, message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, - suggested_post_parameters=None): + suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendLocation' payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude} if live_period: @@ -826,6 +835,10 @@ def send_location( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload) @@ -882,7 +895,8 @@ def send_venue( foursquare_id=None, foursquare_type=None, disable_notification=None, reply_markup=None, timeout=None, google_place_id=None, google_place_type=None, protect_content=None, message_thread_id=None, reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendVenue' payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address} if foursquare_id: @@ -915,6 +929,10 @@ def send_venue( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload) @@ -922,7 +940,8 @@ def send_contact( token, chat_id, phone_number, first_name, last_name=None, vcard=None, disable_notification=None, reply_markup=None, timeout=None, protect_content=None, message_thread_id=None, reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendContact' payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name} if last_name: @@ -951,6 +970,10 @@ def send_contact( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload) @@ -986,7 +1009,8 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_markup=N thumbnail=None, width=None, height=None, caption_entities=None, protect_content=None, message_thread_id=None, has_spoiler=None, reply_parameters=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, allow_paid_broadcast=None, - cover=None, start_timestamp=None, direct_messages_topic_id=None, suggested_post_parameters=None): + cover=None, start_timestamp=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendVideo' payload = {'chat_id': chat_id} files = None @@ -1028,6 +1052,10 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_markup=N payload['message_thread_id'] = message_thread_id if has_spoiler is not None: payload['has_spoiler'] = has_spoiler + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id if reply_parameters is not None: payload['reply_parameters'] = reply_parameters.to_json() if business_connection_id: @@ -1061,7 +1089,8 @@ def send_animation( parse_mode=None, disable_notification=None, timeout=None, thumbnail=None, caption_entities=None, protect_content=None, width=None, height=None, message_thread_id=None, reply_parameters=None, has_spoiler=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendAnimation' payload = {'chat_id': chat_id} files = None @@ -1113,6 +1142,10 @@ def send_animation( payload['allow_paid_broadcast'] = allow_paid_broadcast if direct_messages_topic_id is not None: payload['direct_messages_topic_id'] = direct_messages_topic_id + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() return _make_request(token, method_url, params=payload, files=files, method='post') @@ -1121,7 +1154,8 @@ def send_animation( def send_voice(token, chat_id, voice, caption=None, duration=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption_entities=None, protect_content=None, message_thread_id=None, reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendVoice' payload = {'chat_id': chat_id} files = None @@ -1159,13 +1193,18 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_markup= payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, files=files, method='post') def send_video_note(token, chat_id, data, duration=None, length=None, reply_markup=None, disable_notification=None, timeout=None, thumbnail=None, protect_content=None, message_thread_id=None, reply_parameters=None,business_connection_id=None, message_effect_id=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendVideoNote' payload = {'chat_id': chat_id} files = None @@ -1209,13 +1248,18 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_mark payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, files=files, method='post') def send_audio(token, chat_id, audio, caption=None, duration=None, performer=None, title=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumbnail=None, caption_entities=None, protect_content=None, message_thread_id=None, reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendAudio' payload = {'chat_id': chat_id} files = None @@ -1265,6 +1309,10 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, files=files, method='post') @@ -1272,7 +1320,8 @@ def send_data(token, chat_id, data, data_type, reply_markup=None, parse_mode=Non disable_notification=None, timeout=None, caption=None, thumbnail=None, caption_entities=None, disable_content_type_detection=None, visible_file_name=None, protect_content = None, message_thread_id=None, emoji=None, reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = get_method_by_type(data_type) payload = {'chat_id': chat_id} files = None @@ -1323,6 +1372,10 @@ def send_data(token, chat_id, data, data_type, reply_markup=None, parse_mode=Non payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return _make_request(token, method_url, params=payload, files=files, method='post') @@ -2831,6 +2884,67 @@ def stop_poll(token, chat_id, message_id, reply_markup=None, business_connection if business_connection_id: payload['business_connection_id'] = business_connection_id return _make_request(token, method_url, params=payload) + +def edit_ephemeral_message_text(token, chat_id, receiver_user_id, ephemeral_message_id, text, parse_mode=None, + entities=None, link_preview_options=None, reply_markup=None): + method_url = r'editEphemeralMessageText' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id, + 'text': text + } + if parse_mode: + payload['parse_mode'] = parse_mode + if entities: + payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities)) + if link_preview_options: + payload['link_preview_options'] = link_preview_options.to_json() + if reply_markup: + payload['reply_markup'] = _convert_markup(reply_markup) + return _make_request(token, method_url, params=payload) + +def edit_ephemeral_message_media(token, chat_id, receiver_user_id, ephemeral_message_id, media, reply_markup=None): + method_url = r'editEphemeralMessageMedia' + media_json, files = convert_input_media(media) + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id, + 'media': media_json + } + if reply_markup: + payload['reply_markup'] = _convert_markup(reply_markup) + return _make_request(token, method_url, params=payload, files=files) + +def edit_ephemeral_message_caption(token, chat_id, receiver_user_id, ephemeral_message_id, caption=None, + parse_mode=None, caption_entities=None, reply_markup=None): + method_url = r'editEphemeralMessageCaption' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + if caption: + payload['caption'] = caption + if parse_mode: + payload['parse_mode'] = parse_mode + if caption_entities: + payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities)) + if reply_markup: + payload['reply_markup'] = _convert_markup(reply_markup) + return _make_request(token, method_url, params=payload) + +def edit_ephemeral_message_reply_markup(token, chat_id, receiver_user_id, ephemeral_message_id, reply_markup=None): + method_url = r'editEphemeralMessageReplyMarkup' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + if reply_markup: + 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' @@ -2865,6 +2979,16 @@ def delete_messages(token, chat_id, message_ids): } return _make_request(token, method_url, params=payload) + +def delete_ephemeral_message(token, chat_id, receiver_user_id, ephemeral_message_id): + method_url = 'deleteEphemeralMessage' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + return _make_request(token, method_url, params=payload) + def delete_message_reaction(token, chat_id, message_id, user_id=None, actor_chat_id=None): method_url = 'deleteMessageReaction' payload = { diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index b2566d02b..15766b0f0 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -187,6 +187,7 @@ def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[ self.purchased_paid_media_handlers = [] self.managed_bot_handlers = [] self.guest_message_handlers = [] + self.subscription_handlers = [] self.custom_filters = {} self.state_handlers = [] @@ -652,6 +653,7 @@ async def process_new_updates(self, updates: List[types.Update]): new_purchased_paid_media = None new_managed_bots = None new_guest_messages = None + new_subscriptions = None for update in updates: @@ -731,6 +733,9 @@ async def process_new_updates(self, updates: List[types.Update]): if update.guest_message: if new_guest_messages is None: new_guest_messages = [] new_guest_messages.append(update.guest_message) + if update.subscription: + if new_subscriptions is None: new_subscriptions = [] + new_subscriptions.append(update.subscription) if new_messages: @@ -781,6 +786,8 @@ async def process_new_updates(self, updates: List[types.Update]): await self.process_new_managed_bots(new_managed_bots) if new_guest_messages: await self.process_new_guest_message(new_guest_messages) + if new_subscriptions: + await self.process_new_subscriptions(new_subscriptions) async def process_new_messages(self, new_messages): """ @@ -933,6 +940,12 @@ async def process_new_guest_message(self, new_guest_messages): """ await self._process_updates(self.guest_message_handlers, new_guest_messages, 'guest_message') + async def process_new_subscriptions(self, new_subscriptions): + """ + :meta private: + """ + await self._process_updates(self.subscription_handlers, new_subscriptions, 'subscription') + async def _get_middlewares(self, update_type): """ :meta private: @@ -2774,6 +2787,54 @@ def register_guest_message_handler(self, callback: Callable, func: Optional[Call handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) self.add_guest_message_handler(handler_dict) + def subscription_handler(self, func=None, **kwargs): + """ + User payment subscription has changed. + + :param func: Function executed as a filter + :type func: :obj:`function` + + :param kwargs: Optional keyword arguments(custom filters) + :return: None + """ + def decorator(handler): + handler_dict = self._build_handler_dict(handler, func=func, **kwargs) + self.add_subscription_handler(handler_dict) + return handler + + return decorator + + def add_subscription_handler(self, handler_dict): + """ + Adds a subscription handler. + Note that you should use register_subscription_handler to add subscription_handler to the bot. + + :meta private: + + :param handler_dict: + :return: + """ + self.subscription_handlers.append(handler_dict) + + def register_subscription_handler(self, callback: Callable, func: Optional[Callable]=None, pass_bot: Optional[bool]=False, **kwargs): + """ + Registers subscription handler. + + :param callback: function to be called + :type callback: :obj:`function` + + :param func: Function executed as a filter + :type func: :obj:`function` + + :param pass_bot: True if you need to pass TeleBot instance to handler(useful for separating handlers into different files) + :type pass_bot: :obj:`bool` + + :param kwargs: Optional keyword arguments(custom filters) + :return: None + """ + handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) + self.add_subscription_handler(handler_dict) + @staticmethod def _build_handler_dict(handler, pass_bot=False, **filters): @@ -3408,7 +3469,9 @@ async def send_message( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send text messages. @@ -3480,6 +3543,14 @@ async def send_message( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -3536,7 +3607,8 @@ async def send_message( reply_markup, parse_mode, disable_notification, timeout, entities, protect_content, message_thread_id, reply_parameters, link_preview_options, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4174,7 +4246,9 @@ async def send_photo( show_caption_above_media: Optional[bool]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send photos. On success, the sent Message is returned. @@ -4248,6 +4322,14 @@ async def send_photo( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4282,7 +4364,8 @@ async def send_photo( parse_mode, disable_notification, timeout, caption_entities, protect_content, message_thread_id, has_spoiler, reply_parameters, business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4400,7 +4483,9 @@ async def send_audio( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .MP3 or .M4A format. On success, the sent Message is returned. Bots can currently send audio files of up to 50 MB in size, @@ -4489,6 +4574,14 @@ async def send_audio( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4526,7 +4619,8 @@ async def send_audio( self.token, chat_id, audio, caption, duration, performer, title, reply_markup, parse_mode, disable_notification, timeout, thumbnail, caption_entities, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4547,7 +4641,9 @@ async def send_voice( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS, or in .MP3 format, or in .M4A format (other formats may be sent as Audio or Document). On success, the sent Message is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. @@ -4617,7 +4713,16 @@ async def send_voice( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. + :rtype: :class:`telebot.types.Message` """ parse_mode = self.parse_mode if (parse_mode is None) else parse_mode disable_notification = self.disable_notification if (disable_notification is None) else disable_notification @@ -4649,7 +4754,8 @@ async def send_voice( self.token, chat_id, voice, caption, duration, reply_markup, parse_mode, disable_notification, timeout, caption_entities, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, - allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4675,7 +4781,9 @@ async def send_document( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send general files. @@ -4757,6 +4865,14 @@ async def send_document( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4806,7 +4922,8 @@ async def send_document( caption_entities = caption_entities, disable_content_type_detection = disable_content_type_detection, visible_file_name = visible_file_name, protect_content = protect_content, message_thread_id = message_thread_id, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4826,7 +4943,9 @@ async def send_sticker( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send static .WEBP, animated .TGS, or video .WEBM stickers. On success, the sent Message is returned. @@ -4891,6 +5010,14 @@ async def send_sticker( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -4930,7 +5057,7 @@ async def send_sticker( disable_notification=disable_notification, timeout=timeout, protect_content=protect_content, message_thread_id=message_thread_id, emoji=emoji, reply_parameters=reply_parameters, business_connection_id=business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - suggested_post_parameters=suggested_post_parameters, direct_messages_topic_id=direct_messages_topic_id + suggested_post_parameters=suggested_post_parameters, direct_messages_topic_id=direct_messages_topic_id,receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -4962,7 +5089,9 @@ async def send_video( cover: Optional[Union[Any, str]]=None, start_timestamp: Optional[int]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). @@ -5062,6 +5191,14 @@ async def send_video( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -5105,7 +5242,8 @@ async def send_video( parse_mode, supports_streaming, disable_notification, timeout, thumbnail, width, height, caption_entities, protect_content, message_thread_id, has_spoiler, reply_parameters, business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, cover=cover, start_timestamp=start_timestamp, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters)) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id)) async def send_animation( self, chat_id: Union[int, str], animation: Union[Any, str], @@ -5131,7 +5269,9 @@ async def send_animation( show_caption_above_media: Optional[bool]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=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. @@ -5223,6 +5363,14 @@ async def send_animation( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -5261,7 +5409,10 @@ async def send_animation( reply_markup, parse_mode, disable_notification, timeout, thumbnail, caption_entities, width, height, protect_content, message_thread_id, has_spoiler, reply_parameters, business_connection_id, message_effect_id=message_effect_id, show_caption_above_media=show_caption_above_media, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters)) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id + ) + ) async def send_video_note( self, chat_id: Union[int, str], data: Union[Any, str], @@ -5281,7 +5432,9 @@ async def send_video_note( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ As of v.4.0, Telegram clients support rounded square MPEG4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent Message is returned. @@ -5355,6 +5508,14 @@ async def send_video_note( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -5391,7 +5552,7 @@ async def send_video_note( self.token, chat_id, data, duration, length, reply_markup, disable_notification, timeout, thumbnail, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, - suggested_post_parameters=suggested_post_parameters + suggested_post_parameters=suggested_post_parameters,receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -5595,7 +5756,9 @@ async def send_location( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send point on the map. On success, the sent Message is returned. @@ -5667,6 +5830,14 @@ async def send_location( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -5700,7 +5871,7 @@ async def send_location( reply_markup, disable_notification, timeout, horizontal_accuracy, heading, proximity_alert_radius, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters,receiver_user_id=receiver_user_id, callback_query_id=callback_query_id ) ) @@ -5830,7 +6001,9 @@ async def send_venue( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send information about a venue. On success, the sent Message is returned. @@ -5910,6 +6083,14 @@ async def send_venue( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -5942,7 +6123,9 @@ async def send_venue( self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type, disable_notification, reply_markup, timeout, google_place_id, google_place_type, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, - allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters) + allow_paid_broadcast=allow_paid_broadcast, direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id + ) ) async def send_contact( @@ -5961,7 +6144,9 @@ async def send_contact( message_effect_id: Optional[str]=None, allow_paid_broadcast: Optional[bool]=None, direct_messages_topic_id: Optional[int]=None, - suggested_post_parameters: Optional[types.SuggestedPostParameters]=None) -> types.Message: + suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, + receiver_user_id: Optional[int]=None, + callback_query_id: Optional[str]=None) -> types.Message: """ Use this method to send phone contacts. On success, the sent Message is returned. @@ -6028,6 +6213,14 @@ async def send_contact( is automatically declined. :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters` + :param receiver_user_id: For outgoing ephemeral messages, unique identifier of the user who will receive the message; + for group and supergroup chats only. It is not guaranteed that the user will receive the message, especially + if they are offline. See ephemeral message sending for more details. + :type receiver_user_id: :obj:`int` + + :param callback_query_id: For outgoing ephemeral messages, identifier of the callback query which triggered the message if any + :type callback_query_id: :obj:`str` + :return: On success, the sent Message is returned. :rtype: :class:`telebot.types.Message` """ @@ -6061,7 +6254,10 @@ async def send_contact( disable_notification, reply_markup, timeout, protect_content, message_thread_id, reply_parameters, business_connection_id, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, - direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters)) + direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters, + receiver_user_id=receiver_user_id, callback_query_id=callback_query_id + ) + ) async def send_rich_message( self, chat_id: Union[int, str], @@ -8293,6 +8489,175 @@ async def stop_poll( :rtype: :obj:`types.Poll` """ return types.Poll.de_json(await asyncio_helper.stop_poll(self.token, chat_id, message_id, reply_markup, business_connection_id)) + + async def edit_ephemeral_message_text( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, text: str, + parse_mode: Optional[str]=None, entities: Optional[List[types.MessageEntity]]=None, + link_preview_options: Optional[types.LinkPreviewOptions]=None, reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit an ephemeral text message. Note that it is not guaranteed that the + user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagetext + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param text: New text of the message, 1-4096 characters after entity parsing + :type text: :obj:`str` + + :param parse_mode: Mode for parsing entities in the message text. See formatting options for more details. + :type parse_mode: :obj:`str` + + :param entities: A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode + :type entities: :obj:`list` of :obj:`MessageEntity` + + :param link_preview_options: Link preview generation options for the message + :type link_preview_options: :obj:`LinkPreviewOptions` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + parse_mode = self.parse_mode if (parse_mode is None) else parse_mode + + return await asyncio_helper.edit_ephemeral_message_text( + self.token, chat_id, receiver_user_id, ephemeral_message_id, text, + parse_mode=parse_mode, entities=entities, + link_preview_options=link_preview_options, reply_markup=reply_markup) + + async def edit_ephemeral_message_media( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, media: types.InputMedia, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit the media of an ephemeral message. Note that it is not guaranteed + that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagemedia + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param media: A JSON-serialized object for the new media content of the message. A new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. + :type media: :obj:`InputMedia` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + return await asyncio_helper.edit_ephemeral_message_media( + self.token, chat_id, receiver_user_id, ephemeral_message_id, media, + reply_markup=reply_markup) + + + async def edit_ephemeral_message_caption( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, caption: Optional[str]=None, + parse_mode: Optional[str]=None, + caption_entities: Optional[List[types.MessageEntity]]=None, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit the caption of an ephemeral message. Note that it is not guaranteed + that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagecaption + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param caption: New caption of the message, 0-1024 characters after entities parsing + :type caption: :obj:`str` + + :param parse_mode: Mode for parsing entities in the message caption. See formatting options for more details. + :type parse_mode: :obj:`str` + + :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode + :type caption_entities: :obj:`list` of :obj:`MessageEntity` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + parse_mode = self.parse_mode if (parse_mode is None) else parse_mode + + return await asyncio_helper.edit_ephemeral_message_caption( + self.token, chat_id, receiver_user_id, ephemeral_message_id, caption, + parse_mode=parse_mode, caption_entities=caption_entities, + reply_markup=reply_markup) + + + async def edit_ephemeral_message_reply_markup( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int, + reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> bool: + """ + Use this method to edit only the reply markup of an ephemeral message. Note that it is + not guaranteed that the user will receive the message edit event, especially if they are offline. On success, True is returned. + + Telegram documentation: https://core.telegram.org/bots/api#editephemeralmessagereplymarkup + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to edit + :type ephemeral_message_id: :obj:`int` + + :param reply_markup: A JSON-serialized object for an inline keyboard + :type reply_markup: :obj:`InlineKeyboardMarkup` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + return await asyncio_helper.edit_ephemeral_message_reply_markup( + self.token, chat_id, receiver_user_id, ephemeral_message_id, reply_markup=reply_markup) + + async def delete_ephemeral_message( + self, chat_id: Union[int, str], receiver_user_id: int, ephemeral_message_id: int) -> bool: + """ + Use this method to delete an ephemeral message. Note that it is not guaranteed that the user + will receive the message deletion event, especially if they are offline. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#deleteephemeralmessage + + :param chat_id: Unique identifier for the target chat or username of the target supergroup in the format @username + :type chat_id: :obj:`int` | :obj:`str` + + :param receiver_user_id: Identifier of the user who received the message + :type receiver_user_id: :obj:`int` + + :param ephemeral_message_id: Identifier of the ephemeral message to delete + :type ephemeral_message_id: :obj:`int` + + :return: On success, True is returned. + :rtype: :obj:`bool` + """ + return await asyncio_helper.delete_ephemeral_message( + self.token, chat_id, receiver_user_id, ephemeral_message_id) async def answer_shipping_query( self, shipping_query_id: str, ok: bool, diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index d531f7b9b..2999c68f5 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -294,7 +294,7 @@ async def send_message( parse_mode=None, disable_notification=None, timeout=None, entities=None, protect_content=None, message_thread_id=None, reply_parameters=None, link_preview_options=None, business_connection_id=None, message_effect_id=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_name = 'sendMessage' params = {'chat_id': str(chat_id), 'text': text} if link_preview_options is not None: @@ -325,7 +325,10 @@ async def send_message( params['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: params['suggested_post_parameters'] = suggested_post_parameters.to_json() - + if receiver_user_id is not None: + params['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + params['callback_query_id'] = callback_query_id return await _process_request(token, method_name, params=params, method='post') async def send_rich_message( @@ -631,7 +634,7 @@ async def send_photo( caption_entities=None, protect_content=None, message_thread_id=None, has_spoiler=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, allow_paid_broadcast=None, - direct_messages_topic_id=None, suggested_post_parameters=None): + direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendPhoto' payload = {'chat_id': chat_id} files = None @@ -673,6 +676,10 @@ async def send_photo( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload, files=files, method='post') async def send_live_photo( @@ -811,7 +818,8 @@ async def send_location( reply_markup=None, disable_notification=None, timeout=None, horizontal_accuracy=None, heading=None, proximity_alert_radius=None, protect_content=None, message_thread_id=None,reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendLocation' payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude} if live_period: @@ -844,6 +852,10 @@ async def send_location( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload) @@ -901,7 +913,8 @@ async def send_venue( reply_markup=None, timeout=None, google_place_id=None, google_place_type=None, protect_content=None, message_thread_id=None,reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, + receiver_user_id=None, callback_query_id=None): method_url = r'sendVenue' payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address} if foursquare_id: @@ -934,6 +947,10 @@ async def send_venue( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload) @@ -941,7 +958,7 @@ async def send_contact( token, chat_id, phone_number, first_name, last_name=None, vcard=None, disable_notification=None, reply_markup=None, timeout=None, protect_content=None, message_thread_id=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendContact' payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name} if last_name: @@ -970,6 +987,10 @@ async def send_contact( payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload) async def send_message_draft( @@ -1002,7 +1023,7 @@ async def send_video(token, chat_id, data, duration=None, caption=None, reply_m thumbnail=None, width=None, height=None, caption_entities=None, protect_content=None, message_thread_id=None, has_spoiler=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, allow_paid_broadcast=None, cover=None, start_timestamp=None, - direct_messages_topic_id=None, suggested_post_parameters=None): + direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendVideo' payload = {'chat_id': chat_id} files = None @@ -1068,6 +1089,10 @@ async def send_video(token, chat_id, data, duration=None, caption=None, reply_m payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload, files=files, method='post') @@ -1076,7 +1101,7 @@ async def send_animation( parse_mode=None, disable_notification=None, timeout=None, thumbnail=None, caption_entities=None, width=None, height=None, protect_content=None, message_thread_id=None, has_spoiler=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, show_caption_above_media=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendAnimation' payload = {'chat_id': chat_id} files = None @@ -1116,6 +1141,10 @@ async def send_animation( payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id if has_spoiler is not None: payload['has_spoiler'] = has_spoiler if business_connection_id: @@ -1136,7 +1165,7 @@ async def send_animation( async def send_voice(token, chat_id, voice, caption=None, duration=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, caption_entities=None, protect_content=None, message_thread_id=None,reply_parameters=None,business_connection_id=None, message_effect_id=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendVoice' payload = {'chat_id': chat_id} files = None @@ -1174,13 +1203,17 @@ async def send_voice(token, chat_id, voice, caption=None, duration=None, reply_ payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload, files=files, method='post') async def send_video_note(token, chat_id, data, duration=None, length=None, reply_markup=None, disable_notification=None, timeout=None, thumbnail=None, protect_content=None, message_thread_id=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, allow_paid_broadcast=None, - direct_messages_topic_id=None, suggested_post_parameters=None): + direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendVideoNote' payload = {'chat_id': chat_id} files = None @@ -1224,13 +1257,17 @@ async def send_video_note(token, chat_id, data, duration=None, length=None, rep payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload, files=files, method='post') async def send_audio(token, chat_id, audio, caption=None, duration=None, performer=None, title=None, reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumbnail=None, caption_entities=None, protect_content=None, message_thread_id=None,reply_parameters=None, business_connection_id=None, - message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + message_effect_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = r'sendAudio' payload = {'chat_id': chat_id} files = None @@ -1270,6 +1307,10 @@ async def send_audio(token, chat_id, audio, caption=None, duration=None, perform payload['protect_content'] = protect_content if message_thread_id: payload['message_thread_id'] = message_thread_id + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id if business_connection_id: payload['business_connection_id'] = business_connection_id if message_effect_id: @@ -1287,7 +1328,7 @@ async def send_data(token, chat_id, data, data_type, reply_markup=None, parse_m disable_notification=None, timeout=None, caption=None, thumbnail=None, caption_entities=None, disable_content_type_detection=None, visible_file_name=None, protect_content=None, message_thread_id=None, emoji=None,reply_parameters=None, business_connection_id=None, message_effect_id=None, - allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None): + allow_paid_broadcast=None, direct_messages_topic_id=None, suggested_post_parameters=None, receiver_user_id=None, callback_query_id=None): method_url = await get_method_by_type(data_type) payload = {'chat_id': chat_id} files = None @@ -1338,6 +1379,10 @@ async def send_data(token, chat_id, data, data_type, reply_markup=None, parse_m payload['direct_messages_topic_id'] = direct_messages_topic_id if suggested_post_parameters is not None: payload['suggested_post_parameters'] = suggested_post_parameters.to_json() + if receiver_user_id is not None: + payload['receiver_user_id'] = receiver_user_id + if callback_query_id is not None: + payload['callback_query_id'] = callback_query_id return await _process_request(token, method_url, params=payload, files=files, method='post') @@ -2955,6 +3000,79 @@ async def stop_poll(token, chat_id, message_id, reply_markup=None, business_conn payload['business_connection_id'] = business_connection_id return await _process_request(token, method_url, params=payload) +async def edit_ephemeral_message_text(token, chat_id, receiver_user_id, ephemeral_message_id, text, + parse_mode=None, entities=None, link_preview_options=None, + reply_markup=None): + method_url = r'editEphemeralMessageText' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id, + 'text': text + } + if parse_mode: + payload['parse_mode'] = parse_mode + if entities: + payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities)) + if link_preview_options: + payload['link_preview_options'] = json.dumps(link_preview_options.to_dict()) + if reply_markup: + payload['reply_markup'] = await _convert_markup(reply_markup) + return await _process_request(token, method_url, params=payload) + + +async def edit_ephemeral_message_media(token, chat_id, receiver_user_id, ephemeral_message_id, media, + reply_markup=None): + method_url = r'editEphemeralMessageMedia' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + media_json, files = await convert_input_media(media) + payload['media'] = media_json + if reply_markup: + payload['reply_markup'] = await _convert_markup(reply_markup) + return await _process_request(token, method_url, params=payload, files=files if files else None) + +async def delete_ephemeral_message(token, chat_id, receiver_user_id, ephemeral_message_id): + method_url = r'deleteEphemeralMessage' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + return await _process_request(token, method_url, params=payload) + +async def edit_ephemeral_message_caption(token, chat_id, receiver_user_id, ephemeral_message_id, caption, + parse_mode=None, caption_entities=None, reply_markup=None): + method_url = r'editEphemeralMessageCaption' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + if caption: + payload['caption'] = caption + if parse_mode: + payload['parse_mode'] = parse_mode + if caption_entities: + payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities)) + if reply_markup: + payload['reply_markup'] = await _convert_markup(reply_markup) + return await _process_request(token, method_url, params=payload) + +async def edit_ephemeral_message_reply_markup(token, chat_id, receiver_user_id, ephemeral_message_id, reply_markup): + method_url = r'editEphemeralMessageReplyMarkup' + payload = { + 'chat_id': chat_id, + 'receiver_user_id': receiver_user_id, + 'ephemeral_message_id': ephemeral_message_id + } + if reply_markup: + payload['reply_markup'] = await _convert_markup(reply_markup) + return await _process_request(token, method_url, params=payload) + # exceptions class ApiException(Exception): """ diff --git a/telebot/types.py b/telebot/types.py index a0575672e..1089d111b 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -212,6 +212,9 @@ class Update(JsonDeserializable): :param guest_message: Optional. New guest message. The bot can use the field Message.guest_query_id and the method answerGuestQuery to send a message in response. :type guest_message: :class:`telebot.types.Message` + :param subscription: Optional. User payment subscription has changed + :type subscription: :class:`telebot.types.BotSubscriptionUpdated` + :return: Instance of the class :rtype: :class:`telebot.types.Update` @@ -246,18 +249,19 @@ def de_json(cls, json_string): purchased_paid_media = PaidMediaPurchased.de_json(obj.get('purchased_paid_media')) managed_bot = ManagedBotUpdated.de_json(obj.get('managed_bot')) guest_message = Message.de_json(obj.get('guest_message')) + subscription = BotSubscriptionUpdated.de_json(obj.get('subscription')) return cls(update_id, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, my_chat_member, chat_member, chat_join_request, message_reaction, message_reaction_count, removed_chat_boost, chat_boost, business_connection, business_message, edited_business_message, - deleted_business_messages, purchased_paid_media, managed_bot, guest_message) + deleted_business_messages, purchased_paid_media, managed_bot, guest_message, subscription) def __init__(self, update_id, message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, my_chat_member, chat_member, chat_join_request, message_reaction, message_reaction_count, removed_chat_boost, chat_boost, business_connection, business_message, edited_business_message, - deleted_business_messages, purchased_paid_media, managed_bot, guest_message): + deleted_business_messages, purchased_paid_media, managed_bot, guest_message, subscription): self.update_id: int = update_id self.message: Optional[Message] = message self.edited_message: Optional[Message] = edited_message @@ -284,6 +288,7 @@ def __init__(self, update_id, message, edited_message, channel_post, edited_chan self.purchased_paid_media: Optional[PaidMediaPurchased] = purchased_paid_media self.managed_bot: Optional[ManagedBotUpdated] = managed_bot self.guest_message: Optional[Message] = guest_message + self.subscription: Optional[BotSubscriptionUpdated] = subscription class ChatMemberUpdated(JsonDeserializable): """ @@ -800,6 +805,9 @@ class ChatFullInfo(JsonDeserializable): :param guard_bot: Optional. The bot that processes join request queries in the chat. The field is only available to chat administrators. :type guard_bot: :class:`telebot.types.User` + :param community: Optional. The Community to which the chat belongs + :type community: :class:`telebot.types.Community` + :return: Instance of the class :rtype: :class:`telebot.types.ChatFullInfo` """ @@ -839,6 +847,8 @@ def de_json(cls, json_string): obj['first_profile_audio'] = Audio.de_json(obj['first_profile_audio']) if 'guard_bot' in obj: obj['guard_bot'] = User.de_json(obj['guard_bot']) + if 'community' in obj: + obj['community'] = Community.de_json(obj['community']) return cls(**obj) def __init__(self, id, type, title=None, username=None, first_name=None, @@ -856,7 +866,7 @@ def __init__(self, id, type, title=None, username=None, first_name=None, business_opening_hours=None, personal_chat=None, birthdate=None, can_send_paid_media=None, accepted_gift_types=None, is_direct_messages=None, parent_chat=None, rating=None, paid_message_star_count=None, - unique_gift_colors=None, first_profile_audio=None, guard_bot=None, **kwargs): + unique_gift_colors=None, first_profile_audio=None, guard_bot=None, community=None, **kwargs): self.id: int = id self.type: str = type self.title: Optional[str] = title @@ -909,6 +919,7 @@ def __init__(self, id, type, title=None, username=None, first_name=None, self.unique_gift_colors: Optional[UniqueGiftColors] = unique_gift_colors self.first_profile_audio: Optional[Audio] = first_profile_audio self.guard_bot: Optional[User] = guard_bot + self.community: Optional[Community] = community @property @@ -1020,6 +1031,13 @@ class Message(JsonDeserializable): :param sender_tag: Optional. The tag of the message sender in the chat :type sender_tag: :obj:`str` + :param receiver_user: Optional. For ephemeral messages, the user who received the message + :type receiver_user: :class:`telebot.types.User` + + :param ephemeral_message_id: Optional. For ephemeral messages, identifier of the ephemeral message inside this chat. + The identifier may be reused for another ephemeral message after the message is deleted or expires. + :type ephemeral_message_id: :obj:`int` + :param sender_business_bot info: Optional. Information about the business bot that sent the message :type sender_business_bot_info: :class:`telebot.types.User` @@ -1299,6 +1317,12 @@ class Message(JsonDeserializable): :param checklist_tasks_added: Optional. Service message: tasks were added to a checklist :type checklist_tasks_added: :class:`telebot.types.ChecklistTasksAdded` + :param community_chat_added: Optional. Service message: chat added to a Community + :type community_chat_added: :class:`telebot.types.CommunityChatAdded` + + :param community_chat_removed: Optional. Service message: chat removed from a Community + :type community_chat_removed: :class:`telebot.types.CommunityChatRemoved` + :param direct_message_price_changed: Optional. Service message: the price for paid messages in the corresponding direct messages chat of a channel has changed :type direct_message_price_changed: :class:`telebot.types.DirectMessagePriceChanged` @@ -1695,6 +1719,16 @@ def de_json(cls, json_string): if 'rich_message' in obj: opts['rich_message'] = RichMessage.de_json(obj['rich_message']) content_type = 'rich_message' + if 'receiver_user' in obj: + opts['receiver_user'] = User.de_json(obj['receiver_user']) + if 'ephemeral_message_id' in obj: + opts['ephemeral_message_id'] = obj['ephemeral_message_id'] + if 'community_chat_added' in obj: + opts['community_chat_added'] = CommunityChatAdded.de_json(obj['community_chat_added']) + content_type = 'community_chat_added' + if 'community_chat_removed' in obj: + opts['community_chat_removed'] = CommunityChatRemoved.de_json(obj['community_chat_removed']) + content_type = 'community_chat_removed' return cls(message_id, from_user, date, chat, content_type, opts, json_string) @classmethod @@ -4049,6 +4083,9 @@ class BotCommand(JsonSerializable, JsonDeserializable, Dictionaryable): :param description: Description of the command; 1-256 characters. :type description: :obj:`str` + :param is_ephemeral: Optional. True, if the command sends an ephemeral message, which can be seen only by the sender of the message and the bot + :type is_ephemeral: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.BotCommand` """ @@ -4058,15 +4095,19 @@ def de_json(cls, json_string): obj = cls.check_json(json_string, dict_copy=False) return cls(**obj) - def __init__(self, command, description, **kwargs): + def __init__(self, command: str, description: str, is_ephemeral: Optional[bool] = None, **kwargs): self.command: str = command self.description: str = description + self.is_ephemeral: Optional[bool] = is_ephemeral def to_json(self): return json.dumps(self.to_dict()) def to_dict(self): - return {'command': self.command, 'description': self.description} + data = {'command': self.command, 'description': self.description} + if self.is_ephemeral is not None: + data['is_ephemeral'] = self.is_ephemeral + return data # BotCommandScopes @@ -10011,13 +10052,19 @@ class ReplyParameters(JsonDeserializable, Dictionaryable, JsonSerializable): Telegram documentation: https://core.telegram.org/bots/api#replyparameters - :param message_id: Identifier of the message that will be replied to in the current chat, or in the chat chat_id if it is specified + :param message_id: Optional. Identifier of the message that will be replied to in the current chat, + or in the chat chat_id if it is specified. Required if ephemeral_message_id isn't specified. :type message_id: :obj:`int` :param chat_id: Optional. If the message to be replied to is from a different chat, unique identifier for the chat or username of the channel (in the format @channelusername) :type chat_id: :obj:`int` or :obj:`str` + :param ephemeral_message_id: Optional. Identifier of the incoming ephemeral message that will be replied to in the current chat. + A reply to an ephemeral message must itself be an ephemeral message. An ephemeral message may only be replied to within 15 seconds + of being sent. Required if message_id isn't specified. + :type ephemeral_message_id: :obj:`int` + :param allow_sending_without_reply: Optional. Pass True if the message should be sent even if the specified message to be replied to is not found; can be used only for replies in the same chat and forum topic. :type allow_sending_without_reply: :obj:`bool` @@ -10055,11 +10102,11 @@ def de_json(cls, json_string): obj['quote_entities'] = [MessageEntity.de_json(entity) for entity in obj['quote_entities']] return cls(**obj) - def __init__(self, message_id: int, chat_id: Optional[Union[int, str]] = None, + def __init__(self, message_id: Optional[int] = None, chat_id: Optional[Union[int, str]] = None, allow_sending_without_reply: Optional[bool] = None, quote: Optional[str] = None, quote_parse_mode: Optional[str] = None, quote_entities: Optional[List[MessageEntity]] = None, quote_position: Optional[int] = None, checklist_task_id: Optional[int] = None, - poll_option_id: Optional[str] = None, **kwargs) -> None: + poll_option_id: Optional[str] = None, ephemeral_message_id: Optional[int] = None, **kwargs) -> None: self.message_id: int = message_id self.chat_id: Optional[Union[int, str]] = chat_id self.allow_sending_without_reply: Optional[bool] = allow_sending_without_reply @@ -10069,6 +10116,10 @@ def __init__(self, message_id: int, chat_id: Optional[Union[int, str]] = None, self.quote_position: Optional[int] = quote_position self.checklist_task_id: Optional[int] = checklist_task_id self.poll_option_id: Optional[str] = poll_option_id + self.ephemeral_message_id: Optional[int] = ephemeral_message_id + + if self.message_id is None and self.ephemeral_message_id is None: + raise ValueError("Either 'message_id' or 'ephemeral_message_id' must be provided.") def to_dict(self) -> dict: json_dict = { @@ -10090,8 +10141,9 @@ def to_dict(self) -> dict: json_dict['checklist_task_id'] = self.checklist_task_id if self.poll_option_id is not None: json_dict['poll_option_id'] = self.poll_option_id + if self.ephemeral_message_id is not None: + json_dict['ephemeral_message_id'] = self.ephemeral_message_id return json_dict - def to_json(self) -> str: return json.dumps(self.to_dict()) @@ -16350,6 +16402,9 @@ class InputRichMessage(Dictionaryable): """ This object represents a rich message to be sent. Exactly one of the fields html or markdown must be used. + :param blocks: Optional. Content of the rich message to send described as a list of blocks + :type blocks: :obj:`list` of :class:`InputRichBlock` + :param html: Optional. Content of the rich message to send described using HTML formatting. See rich message formatting options for more details. :type html: :obj:`str` @@ -16358,6 +16413,10 @@ class InputRichMessage(Dictionaryable): See rich message formatting options for more details. :type markdown: :obj:`str` + :param media: Optional. List of media that are specified in the markdown or html fields + using tg://photo?id=, tg://video?id=, and tg://audio?id= links + :type media: :obj:`list` of :class:`InputRichMessageMedia` + :param is_rtl: Optional. Pass True if the rich message must be shown right-to-left :type is_rtl: :obj:`bool` @@ -16368,11 +16427,14 @@ class InputRichMessage(Dictionaryable): :return: Instance of the class :rtype: :class:`InputRichMessage` """ - def __init__(self, html: Optional[str] = None, markdown: Optional[str] = None, is_rtl: Optional[bool] = None, skip_entity_detection: Optional[bool] = None, **kwargs): + def __init__(self, html: Optional[str] = None, markdown: Optional[str] = None, is_rtl: Optional[bool] = None, skip_entity_detection: Optional[bool] = None, + media: Optional[List[InputRichMessageMedia]] = None, blocks: Optional[List[InputRichBlock]] = None, **kwargs): self.html: Optional[str] = html self.markdown: Optional[str] = markdown self.is_rtl: Optional[bool] = is_rtl self.skip_entity_detection: Optional[bool] = skip_entity_detection + self.media: Optional[List[InputRichMessageMedia]] = media + self.blocks: Optional[List[InputRichBlock]] = blocks def to_dict(self) -> dict: data = {} @@ -16384,6 +16446,10 @@ def to_dict(self) -> dict: data['is_rtl'] = self.is_rtl if self.skip_entity_detection is not None: data['skip_entity_detection'] = self.skip_entity_detection + if self.media is not None: + data['media'] = [m.to_dict() for m in self.media] + if self.blocks is not None: + data['blocks'] = [b.to_dict() for b in self.blocks] return data def to_json(self) -> str: @@ -16410,3 +16476,923 @@ def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) return cls(**obj) + + +class InputMediaVoiceNote(InputMedia): + """ + Represents a voice message file to be sent. + + Telegram documentation: https://core.telegram.org/bots/api#inputmediavoicenote + + :param type: Type of the media, must be voice_note + :type type: :obj:`str` + + :param media: File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), + pass an HTTP URL for Telegram to get a file from the Internet, or pass "attach://" + to upload a new one using multipart/form-data under name. More information on Sending Files » + :type media: :obj:`str` + + :param caption: Optional. Caption of the voice message to be sent, 0-1024 characters after entities parsing + :type caption: :obj:`str` + + :param parse_mode: Optional. Mode for parsing entities in the voice message caption. See formatting options for more details. + :type parse_mode: :obj:`str` + + :param caption_entities: Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode + :type caption_entities: :obj:`list` of :class:`MessageEntity` + + :param duration: Optional. Duration of the voice message in seconds + :type duration: :obj:`int` + + :return: Instance of the class + :rtype: :class:`InputMediaVoiceNote` + """ + def __init__(self, media: str, caption: Optional[str] = None, parse_mode: Optional[str] = None, caption_entities: Optional[List[MessageEntity]] = None, + duration: Optional[int] = None, **kwargs): + super().__init__(type='voice_note', media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities) + self.duration: Optional[int] = duration + + def to_dict(self) -> dict: + data = super().to_dict() + if self.duration is not None: + data['duration'] = self.duration + return data + + +class InputRichMessageMedia(Dictionaryable): + """ + This object represents a media element embedded in an outgoing rich message. + + Telegram documentation: https://core.telegram.org/bots/api#inputrichmessagemedia + + :param id: Unique identifier of the media used in a tg://photo?id=, tg://video?id=, or tg://audio?id= link. + 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. + :type id: :obj:`str` + + :param media: The media to be sent. Everything except the media itself and its properties is ignored. + :type media: :obj:`InputMediaAnimation` or :obj:`InputMediaAudio` or :obj:`InputMediaPhoto` or :obj:`InputMediaVideo` or :obj:`InputMediaVoiceNote` + + :return: Instance of the class + :rtype: :class:`InputRichMessageMedia` + """ + def __init__(self, id: str, media: Union[InputMediaAnimation, InputMediaAudio, InputMediaPhoto, InputMediaVideo, InputMediaVoiceNote], **kwargs): + self.id: str = id + self.media: Union[InputMediaAnimation, InputMediaAudio, InputMediaPhoto, InputMediaVideo, InputMediaVoiceNote] = media + + def to_dict(self) -> dict: + data = { + 'id': self.id, + 'media': self.media.to_dict() + } + return data + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + +class InputRichBlockListItem(Dictionaryable): + """ + An item of a list to be sent. + + Telegram documentation: https://core.telegram.org/bots/api#inputrichblocklistitem + + :param blocks: The content of the item + :type blocks: :obj:`list` of :class:`InputRichBlock` + + :param has_checkbox: Optional. Pass True if the item has a checkbox + :type has_checkbox: :obj:`bool` + + :param is_checked: Optional. Pass True if the item has a checked checkbox + :type is_checked: :obj:`bool` + + :param value: Optional. For ordered lists, the numeric value of the item label + :type value: :obj:`int` + + :param type: Optional. For ordered lists, the type of the item label; must be one of “a” for lowercase letters, “A” for uppercase letters, + “i” for lowercase Roman numerals, “I” for uppercase Roman numerals, or “1” for decimal numbers + :type type: :obj:`str` + + :return: Instance of the class + :rtype: :class:`InputRichBlockListItem` + """ + def __init__(self, blocks: List[InputRichBlock], has_checkbox: Optional[bool] = None, is_checked: Optional[bool] = None, + value: Optional[int] = None, type: Optional[str] = None, **kwargs): + self.blocks: List[InputRichBlock] = blocks + self.has_checkbox: Optional[bool] = has_checkbox + self.is_checked: Optional[bool] = is_checked + self.value: Optional[int] = value + self.type: Optional[str] = type + + def to_dict(self) -> dict: + data = { + 'blocks': [block.to_dict() for block in self.blocks] + } + if self.has_checkbox is not None: + data['has_checkbox'] = self.has_checkbox + if self.is_checked is not None: + data['is_checked'] = self.is_checked + if self.value is not None: + data['value'] = self.value + if self.type is not None: + data['type'] = self.type + return data + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + +class InputRichBlock(Dictionaryable, JsonSerializable): + """ + This object represents a block in a rich formatted message to be sent. + Currently, it can be any of the following types: + - InputRichBlockParagraph + - InputRichBlockSectionHeading + - InputRichBlockPreformatted + - InputRichBlockFooter + - InputRichBlockDivider + - InputRichBlockMathematicalExpression + - InputRichBlockAnchor + - InputRichBlockList + - InputRichBlockBlockQuotation + - InputRichBlockPullQuotation + - InputRichBlockCollage + - InputRichBlockSlideshow + - InputRichBlockTable + - InputRichBlockDetails + - InputRichBlockMap + - InputRichBlockAnimation + - InputRichBlockAudio + - InputRichBlockPhoto + - InputRichBlockVideo + - InputRichBlockVoiceNote + - InputRichBlockThinking + + Telegram documentation: https://core.telegram.org/bots/api#inputrichblock + + :return: Instance of the class + :rtype: :class:`InputRichBlock` + """ + def __init__(self, type: str, **kwargs): + self.type: str = type + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + def to_dict(self) -> dict: + return { + 'type': self.type + } + + +class InputRichBlockParagraph(InputRichBlock): + """ + A text paragraph, corresponding to the HTML tag

. + + Telegram documentation: https://core.telegram.org/bots/api#inputrichblockparagraph + + :param type: Type of the block, always “paragraph” + :type type: :obj:`str` + + :param text: Text of the block + :type text: :class:`RichText` + + :return: Instance of the class + :rtype: :class:`InputRichBlockParagraph` + """ + def __init__(self, text: RichText, **kwargs): + super().__init__(type='paragraph', **kwargs) + self.text: RichText = text + + def to_dict(self) -> dict: + data = super().to_dict() + data['text'] = self.text.to_dict() + return data + + +class InputRichBlockSectionHeading(InputRichBlock): + """ + A section heading, corresponding to the HTML tags

,

,

,

,

, or
. + + Telegram documentation: https://core.telegram.org/bots/api#inputrichblocksectionheading + + :param type: Type of the block, always “heading” + :type type: :obj:`str` + + :param text: Text of the block + :type text: :class:`RichText` + + :param size: Relative size of the text font; 1-6, 1 is the largest, 6 is the smallest + :type size: :obj:`int` + + :return: Instance of the class + :rtype: :class:`InputRichBlockSectionHeading` + """ + def __init__(self, text: RichText, size: int, **kwargs): + super().__init__(type='heading', **kwargs) + self.text: RichText = text + self.size: int = size + + def to_dict(self) -> dict: + data = super().to_dict() + data['text'] = self.text.to_dict() + data['size'] = self.size + return data + + +class InputRichBlockPreformatted(InputRichBlock): + """ + A preformatted text block, corresponding to the nested HTML tags
 and .
+
+    Telegram documentation: https://core.telegram.org/bots/api#inputrichblockpreformatted
+
+    :param type: Type of the block, always “pre”
+    :type type: :obj:`str`
+
+    :param text: Text of the block
+    :type text: :class:`RichText`
+
+    :param language: Optional. The programming language of the text
+    :type language: :obj:`str`
+
+    :return: Instance of the class
+    :rtype: :class:`InputRichBlockPreformatted`
+    """
+    def __init__(self, text: RichText, language: Optional[str] = None, **kwargs):
+        super().__init__(type='pre', **kwargs)
+        self.text: RichText = text
+        self.language: Optional[str] = language
+
+    def to_dict(self) -> dict:
+        data = super().to_dict()
+        data['text'] = self.text.to_dict()
+        if self.language is not None:
+            data['language'] = self.language
+        return data
+
+
+class InputRichBlockFooter(InputRichBlock):
+    """
+    A footer, corresponding to the HTML tag