diff --git a/README.md b/README.md
index 24a6e7845..374e98b40 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
A simple, but extensible Python implementation for the Telegram Bot API.
Both synchronous and asynchronous.
-## Supported Bot API version:
+##
Supported Bot API version:
diff --git a/telebot/__init__.py b/telebot/__init__.py
index 6a0265c3e..c070afee0 100644
--- a/telebot/__init__.py
+++ b/telebot/__init__.py
@@ -250,6 +250,7 @@ def __init__(
self.deleted_business_messages_handlers = []
self.purchased_paid_media_handlers = []
self.managed_bot_handlers = []
+ self.guest_message_handlers = []
self.custom_filters = {}
self.state_handlers = []
@@ -728,6 +729,7 @@ def process_new_updates(self, updates: List[types.Update]):
new_deleted_business_messages = None
new_purchased_paid_media = None
new_managed_bots = None
+ new_guest_messages = None
for update in updates:
if apihelper.ENABLE_MIDDLEWARE and not self.use_class_middlewares:
@@ -815,6 +817,9 @@ def process_new_updates(self, updates: List[types.Update]):
if update.managed_bot:
if new_managed_bots is None: new_managed_bots = []
new_managed_bots.append(update.managed_bot)
+ if update.guest_message:
+ if new_guest_messages is None: new_guest_messages = []
+ new_guest_messages.append(update.guest_message)
if new_messages:
self.process_new_messages(new_messages)
@@ -864,6 +869,8 @@ def process_new_updates(self, updates: List[types.Update]):
self.process_new_purchased_paid_media(new_purchased_paid_media)
if new_managed_bots:
self.process_new_managed_bot(new_managed_bots)
+ if new_guest_messages:
+ self.process_new_guest_message(new_guest_messages)
def process_new_messages(self, new_messages):
"""
@@ -1011,6 +1018,12 @@ def process_new_managed_bot(self, new_managed_bots):
:meta private:
"""
self._notify_command_handlers(self.managed_bot_handlers, new_managed_bots, 'managed_bot')
+
+ def process_new_guest_message(self, new_guest_messages):
+ """
+ :meta private:
+ """
+ self._notify_command_handlers(self.guest_message_handlers, new_guest_messages, 'guest_message')
def process_middlewares(self, update):
"""
@@ -1601,7 +1614,7 @@ def leave_chat(self, chat_id: Union[int, str]) -> bool:
return apihelper.leave_chat(self.token, chat_id)
- def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.ChatMember]:
+ def get_chat_administrators(self, chat_id: Union[int, str], return_bots: Optional[bool]=None) -> List[types.ChatMember]:
"""
Use this method to get a list of administrators in a chat.
On success, returns an Array of ChatMember objects that contains
@@ -1611,10 +1624,16 @@ def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.ChatMe
:param chat_id: Unique identifier for the target chat or username
of the target supergroup or channel (in the format @channelusername)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param return_bots: Pass True to additionally receive all bots that are administrators of the chat.
+ By default, bots other than the current bot are omitted.
+ :type return_bots: :obj:`bool`
+
:return: List made of ChatMember objects.
:rtype: :obj:`list` of :class:`telebot.types.ChatMember`
"""
- result = apihelper.get_chat_administrators(self.token, chat_id)
+ result = apihelper.get_chat_administrators(self.token, chat_id, return_bots=return_bots)
return [types.ChatMember.de_json(r) for r in result]
@@ -1652,6 +1671,24 @@ def get_chat_member_count(self, chat_id: Union[int, str]) -> int:
:rtype: :obj:`int`
"""
return apihelper.get_chat_member_count(self.token, chat_id)
+
+ def get_user_personal_chat_messages(self, user_id: int, limit: int) -> List[types.Message]:
+ """
+ Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an array of Message objects is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#getuserpersonalchatmessages
+
+ :param user_id: Unique identifier for the target user
+ :type user_id: :obj:`int`
+
+ :param limit: The maximum number of messages to return; 1-20
+ :type limit: :obj:`int`
+
+ :return: List of Message objects.
+ :rtype: :obj:`list` of :class:`telebot.types.Message`
+ """
+ result = apihelper.get_user_personal_chat_messages(self.token, user_id, limit)
+ return [types.Message.de_json(r) for r in result]
def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str) -> types.StickerSet:
@@ -2144,6 +2181,52 @@ def delete_messages(self, chat_id: Union[int, str], message_ids: List[int]):
:return: Returns True on success.
"""
return apihelper.delete_messages(self.token, chat_id, message_ids)
+
+ def delete_message_reaction(self, chat_id: Union[int, str], message_id: int, user_id: Optional[int]=None, actor_chat_id: Optional[int]=None) -> bool:
+ """
+ Use this method to remove a reaction from a message in a group or a supergroup chat.
+ The bot must have the 'can_delete_messages' administrator right in the chat. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#deletemessagereaction
+
+ :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @username)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param message_id: Identifier of the target message
+ :type message_id: :obj:`int`
+
+ :param user_id: Identifier of the user whose reaction will be removed, if the reaction was added by a user
+ :type user_id: :obj:`int`
+
+ :param actor_chat_id: Identifier of the chat whose reaction will be removed, if the reaction was added by a chat
+ :type actor_chat_id: :obj:`int`
+
+ :return: Returns True on success.
+ :rtype: :obj:`bool`
+ """
+ return apihelper.delete_message_reaction(self.token, chat_id, message_id, user_id=user_id, actor_chat_id=actor_chat_id)
+
+ def delete_all_message_reactions(self, chat_id: Union[int, str], user_id: Optional[int]=None, actor_chat_id: Optional[int]=None) -> bool:
+ """
+ Use this method to remove up to 10000 recent reactions in a group or a supergroup chat added by a given user or chat.
+ The bot must have the 'can_delete_messages' administrator right in the chat. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#deleteallmessagereactions
+
+ :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @username)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param user_id: Identifier of the user whose reactions will be removed, if the reactions were added by a user
+ :type user_id: :obj:`int`
+
+ :param actor_chat_id: Identifier of the chat whose reactions will be removed, if the reactions were added by a chat
+ :type actor_chat_id: :obj:`int`
+
+ :return: Returns True on success.
+ :rtype: :obj:`bool`
+ """
+ return apihelper.delete_all_message_reactions(self.token, chat_id, user_id=user_id, actor_chat_id=actor_chat_id)
+
def forward_messages(self, chat_id: Union[str, int], from_chat_id: Union[str, int], message_ids: List[int],
@@ -2564,6 +2647,92 @@ def send_photo(
direct_messages_topic_id=direct_messages_topic_id, suggested_post_parameters=suggested_post_parameters
)
)
+
+ def send_live_photo(
+ self, business_connection_id: Optional[str], chat_id: Union[int, str],
+ live_photo: Union[Any, str], photo: Union[Any, str],
+ caption: Optional[str]=None, parse_mode: Optional[str]=None,
+ caption_entities: Optional[List[types.MessageEntity]]=None, show_caption_above_media: Optional[bool]=None,
+ has_spoiler: Optional[bool]=None, disable_notification: Optional[bool]=None, protect_content: Optional[bool]=None,
+ allow_paid_broadcast: Optional[bool]=None, message_effect_id: Optional[str]=None,
+ suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, reply_parameters: Optional[types.ReplyParameters]=None,
+ reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Message:
+ """
+ Use this method to send live photos. On success, the sent Message is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#sendlivephoto
+
+ :param business_connection_id: Unique identifier of the business connection on behalf of which the message will be sent
+ :type business_connection_id: :obj:`str`
+
+ :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param live_photo: Live photo video to send. The video must be no longer than 10 seconds and must not exceed 10 MB in size.
+ Pass a file_id as String to send a video that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data.
+ More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type live_photo: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :param photo: The static photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data.
+ More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type photo: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
+ :type caption: :obj:`str`
+
+ :param parse_mode: Mode for parsing entities in the video 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 :class:`telebot.types.Message
+
+ :param show_caption_above_media: Pass True, if the caption must be shown above the message media
+ :type show_caption_above_media: :obj:`bool`
+
+ :param has_spoiler: Pass True if the video needs to be covered with a spoiler animation
+ :type has_spoiler: :obj:`bool`
+
+ :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
+ :type disable_notification: :obj:`bool`
+
+ :param protect_content: Protects the contents of the sent message from forwarding and saving
+ :type protect_content: :obj:`bool`
+
+ :param allow_paid_broadcast: Pass True to allow up to 1000 messages per second, ignoring broadcasting limits for a fee of 0.1 Telegram
+ Stars per message. The relevant Stars will be withdrawn from the bot's balance.
+ :type allow_paid_broadcast: :obj:`bool`
+
+ :param message_effect_id: Unique identifier of the message effect to be added to the message; for private chats only
+ :type message_effect_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
+ :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters`
+
+ :param reply_parameters: Additional parameters for replies to messages
+ :type reply_parameters: :class:`telebot.types.ReplyParameters`
+
+ :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
+ to remove a reply keyboard or to force a reply from the user.
+ :type reply_markup: :class:`telebot.types.InlineKeyboardMarkup` or :class:`telebot.types.ReplyKeyboardMarkup` or :class:`telebot.types.ReplyKeyboardRemove`
+ or :class:`telebot.types.ForceReply`
+
+ :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
+ protect_content = self.protect_content if (protect_content is None) else protect_content
+
+ return types.Message.de_json(
+ apihelper.send_live_photo(
+ self.token, business_connection_id, chat_id, live_photo, photo, caption=caption, parse_mode=parse_mode,
+ caption_entities=caption_entities, show_caption_above_media=show_caption_above_media, has_spoiler=has_spoiler, disable_notification=disable_notification,
+ protect_content=protect_content, allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id,
+ suggested_post_parameters=suggested_post_parameters, reply_parameters=reply_parameters, reply_markup=reply_markup
+ )
+ )
+
def send_audio(
self, chat_id: Union[int, str], audio: Union[Any, str],
@@ -3659,7 +3828,8 @@ def send_media_group(
self, chat_id: Union[int, str],
media: List[Union[
types.InputMediaAudio, types.InputMediaDocument,
- types.InputMediaPhoto, types.InputMediaVideo]],
+ types.InputMediaPhoto, types.InputMediaVideo,
+ types.InputMediaLivePhoto]],
disable_notification: Optional[bool]=None,
protect_content: Optional[bool]=None,
reply_to_message_id: Optional[int]=None, # deprecated, for backward compatibility
@@ -4263,7 +4433,7 @@ def send_message_draft(
:param draft_id: Unique identifier of the message draft; must be non-zero. Changes of drafts with the same identifier are animated
:type draft_id: :obj:`int`
- :param text: Text of the message to be sent, 1-4096 characters after entities parsing
+ :param text: Text of the message to be sent, 0-4096 characters after entities parsing. Pass an empty text to show a “Thinking…” placeholder.
:type text: :obj:`str`
:param parse_mode: Mode for parsing entities in the message text. See formatting options for more details.
@@ -5268,6 +5438,42 @@ def replace_managed_bot_token(self, user_id: int) -> str:
"""
return apihelper.replace_managed_bot_token(self.token, user_id)
+ def get_managed_bot_access_settings(self, user_id: int) -> types.BotAccessSettings:
+ """
+ Use this method to get the access settings of a managed bot. Returns a BotAccessSettings object on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#getmanagedbotaccesssettings
+
+ :param user_id: User identifier of the managed bot whose access settings will be returned
+ :type user_id: :obj:`int`
+
+ :return: Returns a BotAccessSettings object on success.
+ :rtype: :class:`telebot.types.BotAccessSettings`
+ """
+ return types.BotAccessSettings.de_json(
+ apihelper.get_managed_bot_access_settings(self.token, user_id)
+ )
+
+ def set_managed_bot_access_settings(self, user_id: int, is_access_restricted: bool, added_user_ids: Optional[List[int]]=None) -> bool:
+ """
+ Use this method to change the access settings of a managed bot. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#setmanagedbotaccesssettings
+
+ :param user_id: User identifier of the managed bot whose access settings will be changed
+ :type user_id: :obj:`int`
+
+ :param is_access_restricted: Pass True, if only selected users can access the bot. The bot's owner can always access it.
+ :type is_access_restricted: :obj:`bool`
+
+ :param added_user_ids: A JSON-serialized list of up to 10 identifiers of users who will have access to the bot in addition to its owner. Ignored if is_access_restricted is false.
+ :type added_user_ids: :obj:`list` of :obj:`int`
+
+ :return: True on success.
+ :rtype: :obj:`bool`
+ """
+ return apihelper.set_managed_bot_access_settings(self.token, user_id, is_access_restricted, added_user_ids=added_user_ids)
+
def set_my_commands(self, commands: List[types.BotCommand],
scope: Optional[types.BotCommandScope]=None,
language_code: Optional[str]=None) -> bool:
@@ -6121,7 +6327,9 @@ def send_poll(
correct_option_ids: Optional[List[int]]=None,
description: Optional[str]=None,
description_parse_mode: Optional[str]=None,
- description_entities: Optional[List[types.MessageEntity]]=None) -> types.Message:
+ description_entities: Optional[List[types.MessageEntity]]=None,
+ members_only: Optional[bool]=None,
+ country_codes: Optional[List[str]]=None) -> types.Message:
"""
Use this method to send a native poll.
On success, the sent Message is returned.
@@ -6220,6 +6428,13 @@ def send_poll(
:param hide_results_until_closes: Pass True, if poll results must be shown only after the poll closes
:type hide_results_until_closes: :obj:`bool`
+ :param members_only: Pass True, if voting is limited to users who have been members of the chat where the poll is being sent for more than 24 hours; for channel chats only
+ :type members_only: :obj:`bool`
+
+ :param country_codes: A JSON-serialized list of 0-12 two-letter ISO 3166-1 alpha-2 country codes indicating the countries from which users can vote in the poll;
+ for channel chats only. If omitted or empty, then users from any country can participate in the poll.
+ :type country_codes: :obj:`list` of :obj:`str`
+
:param correct_option_ids: A JSON-serialized list of monotonically increasing 0-based identifiers of the correct answer options, required for polls in quiz mode
:type correct_option_ids: :obj:`list` of :obj:`int`
@@ -6302,7 +6517,8 @@ def send_poll(
message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast,
allows_revoting=allows_revoting, shuffle_options=shuffle_options,
allow_adding_options=allow_adding_options, hide_results_until_closes=hide_results_until_closes, correct_option_ids=correct_option_ids,
- description=description, description_parse_mode=description_parse_mode, description_entities=description_entities)
+ description=description, description_parse_mode=description_parse_mode, description_entities=description_entities,
+ members_only=members_only, country_codes=country_codes)
)
@@ -6666,6 +6882,26 @@ def answer_callback_query(
"""
return apihelper.answer_callback_query(
self.token, callback_query_id, text=text, show_alert=show_alert, url=url, cache_time=cache_time)
+
+
+ def answer_guest_query(self, guest_query_id: str, result: types.InlineQueryResultBase) -> types.SentGuestMessage:
+ """
+ Use this method to reply to a received guest message. On success, a SentGuestMessage object is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#answerguestquery
+
+ :param guest_query_id: Unique identifier for the query to be answered
+ :type guest_query_id: :obj:`str`
+
+ :param result: A JSON-serialized object describing the message to be sent
+ :type result: :obj:`types.InlineQueryResult`
+
+ :return: On success, a SentGuestMessage object is returned.
+ :rtype: :obj:`types.SentGuestMessage`
+ """
+ return types.SentGuestMessage.de_json(
+ apihelper.answer_guest_query(self.token, guest_query_id, result)
+ )
def get_user_chat_boosts(self, chat_id: Union[int, str], user_id: int) -> types.UserChatBoosts:
@@ -10499,6 +10735,54 @@ def register_managed_bot_handler(self, callback: Callable, func: Optional[Callab
handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs)
self.add_managed_bot_handler(handler_dict)
+ def guest_message_handler(self, func=None, **kwargs):
+ """
+ New guest message. The bot can use the field :field:`telebot.types.Message.is_guest` and the method answerGuestQuery to send a message in response.
+
+ :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_guest_message_handler(handler_dict)
+ return handler
+
+ return decorator
+
+ def add_guest_message_handler(self, handler_dict):
+ """
+ Adds a guest_message handler.
+ Note that you should use register_guest_message_handler to add guest_message_handler to the bot.
+
+ :meta private:
+
+ :param handler_dict:
+ :return:
+ """
+ self.guest_message_handlers.append(handler_dict)
+
+ def register_guest_message_handler(self, callback: Callable, func: Optional[Callable]=None, pass_bot: Optional[bool]=False, **kwargs):
+ """
+ Registers guest message 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_guest_message_handler(handler_dict)
+
def add_custom_filter(self, custom_filter: Union[SimpleCustomFilter, AdvancedCustomFilter]):
"""
diff --git a/telebot/apihelper.py b/telebot/apihelper.py
index 849537ad1..6e5366860 100644
--- a/telebot/apihelper.py
+++ b/telebot/apihelper.py
@@ -391,9 +391,11 @@ def leave_chat(token, chat_id):
return _make_request(token, method_url, params=payload)
-def get_chat_administrators(token, chat_id):
+def get_chat_administrators(token, chat_id, return_bots=None):
method_url = r'getChatAdministrators'
payload = {'chat_id': chat_id}
+ if return_bots is not None:
+ payload['return_bots'] = return_bots
return _make_request(token, method_url, params=payload)
@@ -621,6 +623,53 @@ def send_photo(
payload['suggested_post_parameters'] = suggested_post_parameters.to_json()
return _make_request(token, method_url, params=payload, files=files, method='post')
+def send_live_photo(
+ token, chat_id, live_photo, photo,
+ caption=None, parse_mode=None, caption_entities=None, show_caption_above_media=None, has_spoiler=None,
+ disable_notification=None, protect_content=None, reply_parameters=None, reply_markup=None,
+ business_connection_id=None, message_effect_id=None, allow_paid_broadcast=None,
+ direct_messages_topic_id=None, suggested_post_parameters=None):
+ method_url = r'sendLivePhoto'
+ files = {}
+ payload = {'chat_id': chat_id}
+ if util.is_string(live_photo):
+ payload['live_photo'] = live_photo
+ else:
+ files['live_photo'] = live_photo
+ if util.is_string(photo):
+ payload['photo'] = photo
+ else:
+ files['photo'] = photo
+ 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 show_caption_above_media is not None:
+ payload['show_caption_above_media'] = show_caption_above_media
+ if has_spoiler is not None:
+ payload['has_spoiler'] = has_spoiler
+ if disable_notification is not None:
+ payload['disable_notification'] = disable_notification
+ if protect_content is not None:
+ payload['protect_content'] = protect_content
+ if reply_parameters is not None:
+ payload['reply_parameters'] = reply_parameters.to_json()
+ if reply_markup:
+ payload['reply_markup'] = _convert_markup(reply_markup)
+ if business_connection_id:
+ payload['business_connection_id'] = business_connection_id
+ if message_effect_id:
+ payload['message_effect_id'] = message_effect_id
+ if allow_paid_broadcast is not None:
+ 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 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 or None, method='post')
+
def send_paid_media(
token, chat_id, star_count, media,
caption=None, parse_mode=None, caption_entities=None, show_caption_above_media=None,
@@ -1611,6 +1660,26 @@ def get_managed_bot_token(token, user_id):
payload = {'user_id': user_id}
return _make_request(token, method_url, params=payload , method='post')
+def set_managed_bot_access_settings(token, user_id, is_access_restricted, added_user_ids=None):
+ method_url = 'setManagedBotAccessSettings'
+ payload = {
+ 'user_id': user_id,
+ 'is_access_restricted': is_access_restricted
+ }
+ if added_user_ids is not None:
+ payload['added_user_ids'] = json.dumps(added_user_ids)
+ return _make_request(token, method_url, params=payload , method='post')
+
+def get_user_personal_chat_messages(token, user_id, limit):
+ method_url = 'getUserPersonalChatMessages'
+ payload = {'user_id': user_id, 'limit': limit}
+ return _make_request(token, method_url, params=payload , method='post')
+
+def get_managed_bot_access_settings(token, user_id):
+ method_url = 'getManagedBotAccessSettings'
+ payload = {'user_id': user_id}
+ return _make_request(token, method_url, params=payload , method='post')
+
def replace_managed_bot_token(token, user_id):
method_url = 'replaceManagedBotToken'
payload = {'user_id': user_id}
@@ -2058,6 +2127,10 @@ def answer_callback_query(token, callback_query_id, text=None, show_alert=None,
payload['cache_time'] = cache_time
return _make_request(token, method_url, params=payload, method='post')
+def answer_guest_query(token, guest_query_id, result):
+ method_url = 'answerGuestQuery'
+ payload = {'guest_query_id': guest_query_id, 'result': result.to_json()}
+ return _make_request(token, method_url, params=payload, method='post')
def get_user_chat_boosts(token, chat_id, user_id):
method_url = 'getUserChatBoosts'
@@ -2557,7 +2630,7 @@ def send_poll(
reply_markup=None, timeout=None, explanation_entities=None, protect_content=None, message_thread_id=None,
reply_parameters=None, business_connection_id=None, question_parse_mode=None, question_entities=None, message_effect_id=None,
allow_paid_broadcast=None, allows_revoting=None, shuffle_options=None, allow_adding_options=None, hide_results_until_closes=None,
- correct_option_ids=None, description=None, description_parse_mode=None, description_entities=None):
+ correct_option_ids=None, description=None, description_parse_mode=None, description_entities=None, members_only=None, country_codes=None):
method_url = r'sendPoll'
payload = {
'chat_id': str(chat_id),
@@ -2624,6 +2697,10 @@ def send_poll(
payload['description_parse_mode'] = description_parse_mode
if description_entities is not None:
payload['description_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(description_entities))
+ if members_only is not None:
+ payload['members_only'] = members_only
+ if country_codes is not None:
+ payload['country_codes'] = json.dumps(country_codes)
return _make_request(token, method_url, params=payload)
def create_forum_topic(token, chat_id, name, icon_color=None, icon_custom_emoji_id=None):
@@ -2710,6 +2787,29 @@ def delete_messages(token, chat_id, message_ids):
}
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 = {
+ 'chat_id': chat_id,
+ 'message_id': message_id
+ }
+ if user_id is not None:
+ payload['user_id'] = user_id
+ if actor_chat_id is not None:
+ payload['actor_chat_id'] = actor_chat_id
+ return _make_request(token, method_url, params=payload, method='post')
+
+def delete_all_message_reactions(token, chat_id, user_id=None, actor_chat_id=None):
+ method_url = 'deleteAllMessageReactions'
+ payload = {
+ 'chat_id': chat_id
+ }
+ if user_id is not None:
+ payload['user_id'] = user_id
+ if actor_chat_id is not None:
+ payload['actor_chat_id'] = actor_chat_id
+ return _make_request(token, method_url, params=payload, method='post')
+
def forward_messages(token, chat_id, from_chat_id, message_ids, disable_notification=None,
message_thread_id=None, protect_content=None, direct_messages_topic_id=None):
method_url = 'forwardMessages'
diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py
index 99349a15c..2a69dc8d5 100644
--- a/telebot/async_telebot.py
+++ b/telebot/async_telebot.py
@@ -186,6 +186,7 @@ def __init__(self, token: str, parse_mode: Optional[str]=None, offset: Optional[
self.deleted_business_messages_handlers = []
self.purchased_paid_media_handlers = []
self.managed_bot_handlers = []
+ self.guest_message_handlers = []
self.custom_filters = {}
self.state_handlers = []
@@ -650,6 +651,7 @@ async def process_new_updates(self, updates: List[types.Update]):
new_deleted_business_messages = None
new_purchased_paid_media = None
new_managed_bots = None
+ new_guest_messages = None
for update in updates:
@@ -726,6 +728,9 @@ async def process_new_updates(self, updates: List[types.Update]):
if update.managed_bot:
if new_managed_bots is None: new_managed_bots = []
new_managed_bots.append(update.managed_bot)
+ if update.guest_message:
+ if new_guest_messages is None: new_guest_messages = []
+ new_guest_messages.append(update.guest_message)
if new_messages:
@@ -774,6 +779,8 @@ async def process_new_updates(self, updates: List[types.Update]):
await self.process_new_purchased_paid_media(new_purchased_paid_media)
if new_managed_bots:
await self.process_new_managed_bots(new_managed_bots)
+ if new_guest_messages:
+ await self.process_new_guest_message(new_guest_messages)
async def process_new_messages(self, new_messages):
"""
@@ -920,6 +927,12 @@ async def process_new_managed_bots(self, new_managed_bots):
"""
await self._process_updates(self.managed_bot_handlers, new_managed_bots, 'managed_bot')
+ async def process_new_guest_message(self, new_guest_messages):
+ """
+ :meta private:
+ """
+ await self._process_updates(self.guest_message_handlers, new_guest_messages, 'guest_message')
+
async def _get_middlewares(self, update_type):
"""
:meta private:
@@ -2713,6 +2726,54 @@ def register_managed_bot_handler(self, callback: Callable, func: Optional[Callab
handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs)
self.add_managed_bot_handler(handler_dict)
+ def guest_message_handler(self, func=None, **kwargs):
+ """
+ New guest message. The bot can use the field :field:`telebot.types.Message.is_guest` and the method answerGuestQuery to send a message in response.
+
+ :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_guest_message_handler(handler_dict)
+ return handler
+
+ return decorator
+
+ def add_guest_message_handler(self, handler_dict):
+ """
+ Adds a guest_message handler.
+ Note that you should use register_guest_message_handler to add guest_message_handler to the bot.
+
+ :meta private:
+
+ :param handler_dict:
+ :return:
+ """
+ self.guest_message_handlers.append(handler_dict)
+
+ def register_guest_message_handler(self, callback: Callable, func: Optional[Callable]=None, pass_bot: Optional[bool]=False, **kwargs):
+ """
+ Registers guest message 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_guest_message_handler(handler_dict)
+
@staticmethod
def _build_handler_dict(handler, pass_bot=False, **filters):
@@ -3129,7 +3190,7 @@ async def leave_chat(self, chat_id: Union[int, str]) -> bool:
result = await asyncio_helper.leave_chat(self.token, chat_id)
return result
- async def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.ChatMember]:
+ async def get_chat_administrators(self, chat_id: Union[int, str], return_bots: Optional[bool]=None) -> List[types.ChatMember]:
"""
Use this method to get a list of administrators in a chat.
On success, returns an Array of ChatMember objects that contains
@@ -3139,10 +3200,16 @@ async def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.
:param chat_id: Unique identifier for the target chat or username
of the target supergroup or channel (in the format @channelusername)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param return_bots: Pass True to additionally receive all bots that are administrators of the chat.
+ By default, bots other than the current bot are omitted.
+ :type return_bots: :obj:`bool`
+
:return: List made of ChatMember objects.
:rtype: :obj:`list` of :class:`telebot.types.ChatMember`
"""
- result = await asyncio_helper.get_chat_administrators(self.token, chat_id)
+ result = await asyncio_helper.get_chat_administrators(self.token, chat_id, return_bots=return_bots)
return [types.ChatMember.de_json(r) for r in result]
@util.deprecated(deprecation_text="Use get_chat_member_count instead")
@@ -3180,6 +3247,25 @@ async def get_chat_member_count(self, chat_id: Union[int, str]) -> int:
"""
result = await asyncio_helper.get_chat_member_count(self.token, chat_id)
return result
+
+ async def get_user_personal_chat_messages(self, user_id: int, limit: int) -> List[types.Message]:
+ """
+ Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. On success, an array of Message objects is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#getuserpersonalchatmessages
+
+ :param user_id: Unique identifier for the target user
+ :type user_id: :obj:`int`
+
+ :param limit: The maximum number of messages to return; 1-20
+ :type limit: :obj:`int`
+
+ :return: An array of Message objects.
+ :rtype: :obj:`list` of :class:`telebot.types.Message`
+ """
+
+ result = await asyncio_helper.get_user_personal_chat_messages(self.token, user_id, limit)
+ return [types.Message.de_json(r) for r in result]
async def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str) -> types.StickerSet:
"""
@@ -3744,6 +3830,51 @@ async def delete_messages(self, chat_id: Union[int, str], message_ids: List[int]
"""
return await asyncio_helper.delete_messages(self.token, chat_id, message_ids)
+
+ async def delete_message_reaction(self, chat_id: Union[int, str], message_id: int, user_id: Optional[int]=None, actor_chat_id: Optional[int]=None) -> bool:
+ """
+ Use this method to remove a reaction from a message in a group or a supergroup chat.
+ The bot must have the 'can_delete_messages' administrator right in the chat. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#deletemessagereaction
+
+ :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @username)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param message_id: Identifier of the target message
+ :type message_id: :obj:`int`
+
+ :param user_id: Identifier of the user whose reaction will be removed, if the reaction was added by a user
+ :type user_id: :obj:`int`, optional
+
+ :param actor_chat_id: Identifier of the chat whose reaction will be removed, if the reaction was added by a chat
+ :type actor_chat_id: :obj:`int`, optional
+
+ :return: Returns True on success.
+ :rtype: :obj:`bool`
+ """
+ return await asyncio_helper.delete_message_reaction(self.token, chat_id, message_id, user_id, actor_chat_id)
+
+ async def delete_all_message_reactions(self, chat_id: Union[int, str], user_id: Optional[int]=None, actor_chat_id: Optional[int]=None) -> bool:
+ """
+ Use this method to remove up to 10000 recent reactions in a group or a supergroup chat added by a given user or chat.
+ The bot must have the 'can_delete_messages' administrator right in the chat. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#deleteallmessagereactions
+
+ :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @username)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param user_id: Identifier of the user whose reactions will be removed, if the reactions were added by a user
+ :type user_id: :obj:`int`, optional
+
+ :param actor_chat_id: Identifier of the chat whose reactions will be removed, if the reactions were added by a chat
+ :type actor_chat_id: :obj:`int`, optional
+
+ :return: Returns True on success.
+ :rtype: :obj:`bool`
+ """
+ return await asyncio_helper.delete_all_message_reactions(self.token, chat_id, user_id, actor_chat_id)
async def forward_messages(self, chat_id: Union[str, int], from_chat_id: Union[str, int], message_ids: List[int], disable_notification: Optional[bool]=None,
message_thread_id: Optional[int]=None, protect_content: Optional[bool]=None,
@@ -4155,6 +4286,93 @@ async def send_photo(
)
)
+ async def send_live_photo(
+ self, business_connection_id: str, chat_id: Union[int, str],
+ live_photo: Union[Any, str], photo: Union[Any, str],
+ caption: Optional[str]=None, parse_mode: Optional[str]=None,
+ caption_entities: Optional[List[types.MessageEntity]]=None,
+ show_caption_above_media: Optional[bool]=None, has_spoiler: Optional[bool]=None,
+ disable_notification: Optional[bool]=None, protect_content: Optional[bool]=None,
+ allow_paid_broadcast: Optional[bool]=None, message_effect_id: Optional[str]=None,
+ suggested_post_parameters: Optional[types.SuggestedPostParameters]=None, reply_parameters: Optional[types.ReplyParameters]=None,
+ reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Message:
+ """
+ Use this method to send live photos. On success, the sent Message is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#sendlivephoto
+
+ :param business_connection_id: Unique identifier of the business connection on behalf of which the message will be sent
+ :type business_connection_id: :obj:`str`
+
+ :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername)
+ :type chat_id: :obj:`int` or :obj:`str`
+
+ :param live_photo: Live photo video to send. The video must be no longer than 10 seconds and must not exceed 10 MB in size.
+ Pass a file_id as String to send a video that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data.
+ More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type live_photo: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :param photo: The static photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data.
+ More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type photo: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
+ :type caption: :obj:`str`
+
+ :param parse_mode: Mode for parsing entities in the video 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 :class:`telebot.types.Message
+
+ :param show_caption_above_media: Pass True, if the caption must be shown above the message media
+ :type show_caption_above_media: :obj:`bool`
+
+ :param has_spoiler: Pass True if the video needs to be covered with a spoiler animation
+ :type has_spoiler: :obj:`bool`
+
+ :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
+ :type disable_notification: :obj:`bool`
+
+ :param protect_content: Protects the contents of the sent message from forwarding and saving
+ :type protect_content: :obj:`bool`
+
+ :param allow_paid_broadcast: Pass True to allow up to 1000 messages per second, ignoring broadcasting limits for a fee of 0.1 Telegram
+ Stars per message. The relevant Stars will be withdrawn from the bot's balance.
+ :type allow_paid_broadcast: :obj:`bool`
+
+ :param message_effect_id: Unique identifier of the message effect to be added to the message; for private chats only
+ :type message_effect_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
+ :type suggested_post_parameters: :class:`telebot.types.SuggestedPostParameters`
+
+ :param reply_parameters: Additional parameters for replies to messages
+ :type reply_parameters: :class:`telebot.types.ReplyParameters`
+
+ :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
+ to remove a reply keyboard or to force a reply from the user.
+ :type reply_markup: :class:`telebot.types.InlineKeyboardMarkup` or :class:`telebot.types.ReplyKeyboardMarkup` or :class:`telebot.types.ReplyKeyboardRemove`
+ or :class:`telebot.types.ForceReply`
+
+ :return: On success, the sent Message is returned.
+ :rtype: :class:`telebot.types.Message`
+ """
+ disable_notification = self.disable_notification if (disable_notification is None) else disable_notification
+ protect_content = self.protect_content if (protect_content is None) else protect_content
+
+ if reply_parameters and (reply_parameters.allow_sending_without_reply is None):
+ reply_parameters.allow_sending_without_reply = self.allow_sending_without_reply
+
+ return types.Message.de_json(
+ await asyncio_helper.send_live_photo(
+ self.token, business_connection_id, chat_id, live_photo, photo, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities,
+ show_caption_above_media=show_caption_above_media, has_spoiler=has_spoiler, disable_notification=disable_notification, protect_content=protect_content,
+ allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id, suggested_post_parameters=suggested_post_parameters, reply_parameters=reply_parameters, reply_markup=reply_markup
+ )
+ )
+
async def send_audio(
self, chat_id: Union[int, str], audio: Union[Any, str],
caption: Optional[str]=None, duration: Optional[int]=None,
@@ -5254,7 +5472,8 @@ async def send_media_group(
self, chat_id: Union[int, str],
media: List[Union[
types.InputMediaAudio, types.InputMediaDocument,
- types.InputMediaPhoto, types.InputMediaVideo]],
+ types.InputMediaPhoto, types.InputMediaVideo,
+ types.InputMediaLivePhoto]],
disable_notification: Optional[bool]=None,
protect_content: Optional[bool]=None,
reply_to_message_id: Optional[int]=None,
@@ -5859,7 +6078,7 @@ async def send_message_draft(
:param draft_id: Unique identifier of the message draft; must be non-zero. Changes of drafts with the same identifier are animated
:type draft_id: :obj:`int`
- :param text: Text of the message to be sent, 1-4096 characters after entities parsing
+ :param text: Text of the message to be sent, 0-4096 characters after entities parsing. Pass an empty text to show a “Thinking…” placeholder.
:type text: :obj:`str`
:param parse_mode: Mode for parsing entities in the message text. See formatting options for more details.
@@ -6826,6 +7045,43 @@ async def replace_managed_bot_token(self, user_id: int) -> str:
"""
return await asyncio_helper.replace_managed_bot_token(self.token, user_id)
+ async def get_managed_bot_access_settings(self, user_id: int) -> types.BotAccessSettings:
+ """
+ Use this method to get the access settings of a managed bot. Returns a BotAccessSettings object on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#getmanagedbotaccesssettings
+
+ :param user_id: User identifier of the managed bot whose access settings will be returned
+ :type user_id: :obj:`int`
+
+ :return: Returns a BotAccessSettings object on success.
+ :rtype: :class:`telebot.types.BotAccessSettings`
+ """
+ return types.BotAccessSettings.de_json(
+ await asyncio_helper.get_managed_bot_access_settings(self.token, user_id)
+ )
+
+ async def set_managed_bot_access_settings(self, user_id: int, is_access_restricted: bool,
+ added_user_ids: Optional[List[int]]=None) -> bool:
+ """
+ Use this method to change the access settings of a managed bot. Returns True on success.
+
+ Telegram documentation: https://core.telegram.org/bots/api#setmanagedbotaccesssettings
+
+ :param user_id: User identifier of the managed bot whose access settings will be changed
+ :type user_id: :obj:`int`
+
+ :param is_access_restricted: Pass True, if only selected users can access the bot. The bot's owner can always access it.
+ :type is_access_restricted: :obj:`bool`
+
+ :param added_user_ids: A JSON-serialized list of up to 10 identifiers of users who will have access to the bot in addition to its owner. Ignored if is_access_restricted is false.
+ :type added_user_ids: :obj:`list` of :obj:`int`
+
+ :return: True on success.
+ :rtype: :obj:`bool`
+ """
+ return await asyncio_helper.set_managed_bot_access_settings(self.token, user_id, is_access_restricted, added_user_ids)
+
async def set_my_commands(self, commands: List[types.BotCommand],
scope: Optional[types.BotCommandScope]=None,
language_code: Optional[str]=None) -> bool:
@@ -7650,7 +7906,9 @@ async def send_poll(
correct_option_ids: Optional[List[int]]=None,
description: Optional[str]=None,
description_parse_mode: Optional[str]=None,
- description_entities: Optional[List[types.MessageEntity]]=None) -> types.Message:
+ description_entities: Optional[List[types.MessageEntity]]=None,
+ members_only: Optional[bool]=None,
+ country_codes: Optional[List[str]]=None) -> types.Message:
"""
Use this method to send a native poll.
On success, the sent Message is returned.
@@ -7753,6 +8011,13 @@ async def send_poll(
:param hide_results_until_closes: Pass True, if poll results must be shown only after the poll closes
:type hide_results_until_closes: :obj:`bool`
+ :param members_only: Pass True, if voting is limited to users who have been members of the chat where the poll is being sent for more than 24 hours; for channel chats only
+ :type members_only: :obj:`bool`
+
+ :param country_codes: A JSON-serialized list of 0-12 two-letter ISO 3166-1 alpha-2 country codes indicating the countries from which users can vote in the poll;
+ for channel chats only. If omitted or empty, then users from any country can participate in the poll.
+ :type country_codes: :obj:`list` of :obj:`str`
+
:param correct_option_ids: A JSON-serialized list of monotonically increasing 0-based identifiers of the correct answer options, required for polls in quiz mode
:type correct_option_ids: :obj:`list` of :obj:`int`
@@ -7831,7 +8096,8 @@ async def send_poll(
message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast,
allows_revoting=allows_revoting, shuffle_options=shuffle_options, allow_adding_options=allow_adding_options,
hide_results_until_closes=hide_results_until_closes, correct_option_ids=correct_option_ids, description=description,
- description_parse_mode=description_parse_mode, description_entities=description_entities
+ description_parse_mode=description_parse_mode, description_entities=description_entities,
+ members_only=members_only, country_codes=country_codes
)
)
@@ -8177,12 +8443,23 @@ async def answer_callback_query(
"""
return await asyncio_helper.answer_callback_query(self.token, callback_query_id, text, show_alert, url, cache_time)
-# getUserChatBoosts
-# Use this method to get the list of boosts added to a chat by a user. Requires administrator rights in the chat. Returns a UserChatBoosts object.
-# Parameter Type Required Description
-# chat_id Integer or String Yes Unique identifier for the chat or username of the channel (in the format @channelusername)
-# user_id Integer Yes Unique identifier of the target user
+ async def answer_guest_query(self, guest_query_id: str, result: types.InlineQueryResultBase) -> types.SentGuestMessage:
+ """
+ Use this method to reply to a received guest message. On success, a SentGuestMessage object is returned.
+
+ Telegram documentation: https://core.telegram.org/bots/api#answerguestquery
+
+ :param guest_query_id: Unique identifier for the query to be answered
+ :type guest_query_id: :obj:`str`
+
+ :param result: A JSON-serialized object describing the message to be sent
+ :type result: :obj:`types.InlineQueryResult`
+
+ :return: On success, a SentGuestMessage object is returned.
+ :rtype: :obj:`types.SentGuestMessage`
+ """
+ return types.SentGuestMessage.de_json(await asyncio_helper.answer_guest_query(self.token, guest_query_id, result))
async def get_user_chat_boosts(self, chat_id: Union[int, str], user_id: int) -> types.UserChatBoosts:
"""
diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py
index 7f00b319c..c343609c6 100644
--- a/telebot/asyncio_helper.py
+++ b/telebot/asyncio_helper.py
@@ -381,9 +381,11 @@ async def leave_chat(token, chat_id):
return await _process_request(token, method_url, params=payload)
-async def get_chat_administrators(token, chat_id):
+async def get_chat_administrators(token, chat_id, return_bots=None):
method_url = r'getChatAdministrators'
payload = {'chat_id': chat_id}
+ if return_bots is not None:
+ payload['return_bots'] = return_bots
return await _process_request(token, method_url, params=payload)
@@ -636,6 +638,59 @@ async def send_photo(
if suggested_post_parameters is not None:
payload['suggested_post_parameters'] = suggested_post_parameters.to_json()
return await _process_request(token, method_url, params=payload, files=files, method='post')
+
+async def send_live_photo(
+ token, chat_id, live_photo, photo,
+ caption=None, reply_markup=None,
+ parse_mode=None, disable_notification=None, timeout=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,
+ direct_messages_topic_id=None, suggested_post_parameters=None):
+ method_url = r'sendLivePhoto'
+ payload = {'chat_id': chat_id}
+ files = {}
+ if util.is_string(live_photo):
+ payload['live_photo'] = live_photo
+ else:
+ files['live_photo'] = live_photo
+ if util.is_string(photo):
+ payload['photo'] = photo
+ else:
+ files['photo'] = photo
+ if caption:
+ payload['caption'] = caption
+ if reply_markup:
+ payload['reply_markup'] = await _convert_markup(reply_markup)
+ if parse_mode:
+ payload['parse_mode'] = parse_mode
+ if disable_notification is not None:
+ payload['disable_notification'] = disable_notification
+ if timeout:
+ payload['timeout'] = timeout
+ if caption_entities:
+ payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
+ if reply_parameters is not None:
+ payload['reply_parameters'] = json.dumps(reply_parameters.to_dict())
+ if protect_content is not None:
+ payload['protect_content'] = protect_content
+ if message_thread_id:
+ payload['message_thread_id'] = message_thread_id
+ if has_spoiler is not None:
+ payload['has_spoiler'] = has_spoiler
+ if business_connection_id:
+ payload['business_connection_id'] = business_connection_id
+ if message_effect_id:
+ payload['message_effect_id'] = message_effect_id
+ if show_caption_above_media is not None:
+ payload['show_caption_above_media'] = show_caption_above_media
+ if allow_paid_broadcast is not None:
+ 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 suggested_post_parameters is not None:
+ payload['suggested_post_parameters'] = suggested_post_parameters.to_json()
+ return await _process_request(token, method_url, params=payload, files=files, method='post')
async def send_paid_media(
token, chat_id, star_count, media,
@@ -1623,6 +1678,26 @@ async def get_managed_bot_token(token, user_id):
payload = {'user_id': user_id}
return await _process_request(token, method_url, params=payload , method='post')
+async def set_managed_bot_access_settings(token, user_id, is_access_restricted, added_user_ids=None):
+ method_url = 'setManagedBotAccessSettings'
+ payload = {
+ 'user_id': user_id,
+ 'is_access_restricted': is_access_restricted
+ }
+ if added_user_ids is not None:
+ payload['added_user_ids'] = json.dumps(added_user_ids)
+ return await _process_request(token, method_url, params=payload , method='post')
+
+async def get_user_personal_chat_messages(token, user_id, limit):
+ method_url = 'getUserPersonalChatMessages'
+ payload = {'user_id': user_id, 'limit': limit}
+ return await _process_request(token, method_url, params=payload , method='post')
+
+async def get_managed_bot_access_settings(token, user_id):
+ method_url = 'getManagedBotAccessSettings'
+ payload = {'user_id': user_id}
+ return await _process_request(token, method_url, params=payload , method='post')
+
async def replace_managed_bot_token(token, user_id):
method_url = 'replaceManagedBotToken'
payload = {'user_id': user_id}
@@ -2073,6 +2148,12 @@ async def answer_callback_query(token, callback_query_id, text=None, show_alert=
payload['cache_time'] = cache_time
return await _process_request(token, method_url, params=payload, method='post')
+
+async def answer_guest_query(token, guest_query_id, result):
+ method_url = 'answerGuestQuery'
+ payload = {'guest_query_id': guest_query_id, 'result': result.to_json()}
+ return await _process_request(token, method_url, params=payload, method='post')
+
async def get_user_chat_boosts(token, chat_id, user_id):
method_url = 'getUserChatBoosts'
payload = {'chat_id': chat_id, 'user_id': user_id}
@@ -2537,7 +2618,7 @@ async def send_poll(
reply_markup=None, timeout=None, explanation_entities=None, protect_content=None, message_thread_id=None,
reply_parameters=None,business_connection_id=None, question_parse_mode=None, question_entities=None, message_effect_id=None,
allow_paid_broadcast=None, allows_revoting=None, shuffle_options=None, allow_adding_options=None, hide_results_until_closes=None,
- correct_option_ids=None, description=None, description_parse_mode=None, description_entities=None):
+ correct_option_ids=None, description=None, description_parse_mode=None, description_entities=None, members_only=None, country_codes=None):
method_url = r'sendPoll'
payload = {
'chat_id': str(chat_id),
@@ -2605,6 +2686,10 @@ async def send_poll(
payload['description_parse_mode'] = description_parse_mode
if description_entities is not None:
payload['description_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(description_entities))
+ if members_only is not None:
+ payload['members_only'] = members_only
+ if country_codes is not None:
+ payload['country_codes'] = json.dumps(country_codes)
return await _process_request(token, method_url, params=payload)
@@ -2683,6 +2768,24 @@ async def delete_messages(token, chat_id, message_ids):
}
return await _process_request(token, method_url, params=payload)
+async def delete_message_reaction(token, chat_id, message_id, user_id=None, actor_chat_id=None):
+ method_url = 'deleteMessageReaction'
+ payload = {'chat_id': chat_id, 'message_id': message_id}
+ if user_id is not None:
+ payload['user_id'] = user_id
+ if actor_chat_id is not None:
+ payload['actor_chat_id'] = actor_chat_id
+ return await _process_request(token, method_url, params=payload, method='post')
+
+async def delete_all_message_reactions(token, chat_id, user_id=None, actor_chat_id=None):
+ method_url = 'deleteAllMessageReactions'
+ payload = {'chat_id': chat_id}
+ if user_id is not None:
+ payload['user_id'] = user_id
+ if actor_chat_id is not None:
+ payload['actor_chat_id'] = actor_chat_id
+ return await _process_request(token, method_url, params=payload, method='post')
+
async def forward_messages(token, chat_id, from_chat_id, message_ids, disable_notification=None,
message_thread_id=None, protect_content=None, direct_messages_topic_id=None):
method_url = 'forwardMessages'
diff --git a/telebot/types.py b/telebot/types.py
index 84f8eef8b..33943161f 100644
--- a/telebot/types.py
+++ b/telebot/types.py
@@ -209,6 +209,9 @@ class Update(JsonDeserializable):
:param managed_bot: Optional. A new bot was created to be managed by the bot or token of a bot was changed
:type managed_bot: :class:`telebot.types.ManagedBotUpdated`
+ :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`
+
:return: Instance of the class
:rtype: :class:`telebot.types.Update`
@@ -242,18 +245,19 @@ def de_json(cls, json_string):
deleted_business_messages = BusinessMessagesDeleted.de_json(obj.get('deleted_business_messages'))
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'))
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)
+ deleted_business_messages, purchased_paid_media, managed_bot, guest_message)
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):
+ deleted_business_messages, purchased_paid_media, managed_bot, guest_message):
self.update_id: int = update_id
self.message: Optional[Message] = message
self.edited_message: Optional[Message] = edited_message
@@ -279,6 +283,7 @@ def __init__(self, update_id, message, edited_message, channel_post, edited_chan
self.deleted_business_messages: Optional[BusinessMessagesDeleted] = deleted_business_messages
self.purchased_paid_media: Optional[PaidMediaPurchased] = purchased_paid_media
self.managed_bot: Optional[ManagedBotUpdated] = managed_bot
+ self.guest_message: Optional[Message] = guest_message
class ChatMemberUpdated(JsonDeserializable):
"""
@@ -507,6 +512,9 @@ class User(JsonDeserializable, Dictionaryable, JsonSerializable):
getMe.
:type can_read_all_group_messages: :obj:`bool`
+ :param supports_guest_queries: Optional. True, if the bot supports guest queries from chats it is not a member of. Returned only in getMe.
+ :type supports_guest_queries: :obj:`bool`
+
:param supports_inline_queries: Optional. True, if the bot supports inline queries. Returned only in getMe.
:type supports_inline_queries: :obj:`bool`
@@ -538,7 +546,8 @@ def de_json(cls, json_string):
def __init__(self, id, is_bot, first_name, last_name=None, username=None, language_code=None,
can_join_groups=None, can_read_all_group_messages=None, supports_inline_queries=None,
is_premium=None, added_to_attachment_menu=None, can_connect_to_business=None,
- has_main_web_app=None, has_topics_enabled=None, allows_users_to_create_topics=None, can_manage_bots=None, **kwargs):
+ has_main_web_app=None, has_topics_enabled=None, allows_users_to_create_topics=None, can_manage_bots=None,
+ supports_guest_queries=None, **kwargs):
self.id: int = id
self.is_bot: bool = is_bot
self.first_name: str = first_name
@@ -555,6 +564,8 @@ def __init__(self, id, is_bot, first_name, last_name=None, username=None, langua
self.has_topics_enabled: Optional[bool] = has_topics_enabled
self.allows_users_to_create_topics: Optional[bool] = allows_users_to_create_topics
self.can_manage_bots: Optional[bool] = can_manage_bots
+ self.supports_guest_queries: Optional[bool] = supports_guest_queries
+
@property
def full_name(self) -> str:
"""
@@ -584,7 +595,8 @@ def to_dict(self):
'has_main_web_app': self.has_main_web_app,
'has_topics_enabled': self.has_topics_enabled,
'allows_users_to_create_topics': self.allows_users_to_create_topics,
- 'can_manage_bots': self.can_manage_bots
+ 'can_manage_bots': self.can_manage_bots,
+ 'supports_guest_queries': self.supports_guest_queries
}
@@ -998,6 +1010,11 @@ class Message(JsonDeserializable):
:param date: Date the message was sent in Unix time
:type date: :obj:`int`
+ :param guest_query_id: Optional. The unique identifier for the guest query. Use this identifier with the method answerGuestQuery
+ to send a response message. If non-empty, the message belongs to the chat where the guest bot was summoned, which may not coincide
+ with other existing bot chats sharing the same identifier.
+ :type guest_query_id: :obj:`str`
+
:param business_connection_id: Optional. Unique identifier of the business connection from which the message was received. If non-empty,
the message belongs to a chat of the corresponding business account that is independent from any potential bot chat which might share the same identifier.
:type business_connection_id: :obj:`str`
@@ -1037,6 +1054,12 @@ class Message(JsonDeserializable):
:param via_bot: Optional. Bot through which the message was sent
:type via_bot: :class:`telebot.types.User`
+ :param guest_bot_caller_user: Optional. For a message sent by a guest bot, this is the user whose original message triggered the bot's response
+ :type guest_bot_caller_user: :class:`telebot.types.User`
+
+ :param guest_bot_caller_chat: Optional. For a message sent by a guest bot, this is the chat whose original message triggered the bot's response
+ :type guest_bot_caller_chat: :class:`telebot.types.Chat`
+
:param edit_date: Optional. Date the message was last edited in Unix time
:type edit_date: :obj:`int`
@@ -1089,6 +1112,10 @@ class Message(JsonDeserializable):
:param document: Optional. Message is a general file, information about the file
:type document: :class:`telebot.types.Document`
+ :param live_photo: Optional. Message is a live photo, information about the live photo.
+ For backward compatibility, when this field is set, the photo field will also be set
+ :type live_photo: :class:`telebot.types.LivePhoto`
+
:param paid_media: Optional. Message contains paid media; information about the paid media
:type paid_media: :class:`telebot.types.PaidMediaInfo`
@@ -1638,7 +1665,14 @@ def de_json(cls, json_string):
content_type = 'poll_option_deleted'
if 'reply_to_poll_option_id' in obj:
opts['reply_to_poll_option_id'] = obj['reply_to_poll_option_id']
-
+ if 'guest_bot_caller_user' in obj:
+ opts['guest_bot_caller_user'] = User.de_json(obj['guest_bot_caller_user'])
+ if 'guest_bot_caller_chat' in obj:
+ opts['guest_bot_caller_chat'] = Chat.de_json(obj['guest_bot_caller_chat'])
+ if 'guest_query_id' in obj:
+ opts['guest_query_id'] = obj['guest_query_id']
+ if 'live_photo' in obj:
+ opts['live_photo'] = LivePhoto.de_json(obj['live_photo'])
return cls(message_id, from_user, date, chat, content_type, opts, json_string)
@classmethod
@@ -1781,6 +1815,10 @@ def __init__(self, message_id, from_user, date, chat, content_type, options, jso
self.poll_option_added: Optional[PollOptionAdded] = None
self.poll_option_deleted: Optional[PollOptionDeleted] = None
self.reply_to_poll_option_id: Optional[str] = None
+ self.guest_bot_caller_user: Optional[User] = None
+ self.guest_bot_caller_chat: Optional[Chat] = None
+ self.guest_query_id: Optional[str] = None
+ self.live_photo: Optional[LivePhoto] = None
for key in options:
setattr(self, key, options[key])
@@ -3730,6 +3768,9 @@ class ChatMemberRestricted(ChatMember):
:param can_add_web_page_previews: True, if the user is allowed to add web page previews to their messages
:type can_add_web_page_previews: :obj:`bool`
+ :param can_react_to_messages: True, if the user is allowed to react to messages
+ :type can_react_to_messages: :obj:`bool`
+
:param can_change_info: True, if the user is allowed to change the chat title, photo and other settings
:type can_change_info: :obj:`bool`
@@ -3758,7 +3799,7 @@ def __init__(self, user, status, is_member, can_send_messages, can_send_audios,
can_send_photos, can_send_videos, can_send_video_notes, can_send_voice_notes, can_send_polls,
can_send_other_messages, can_add_web_page_previews,
can_change_info, can_invite_users, can_pin_messages, can_manage_topics,
- until_date=None, tag=None, can_edit_tag=None, **kwargs):
+ until_date=None, tag=None, can_edit_tag=None, can_react_to_messages=None, **kwargs):
super().__init__(user, status, **kwargs)
self.is_member: bool = is_member
self.can_send_messages: bool = can_send_messages
@@ -3778,6 +3819,7 @@ def __init__(self, user, status, is_member, can_send_messages, can_send_audios,
self.until_date: Optional[int] = until_date
self.tag: Optional[str] = tag
self.can_edit_tag: Optional[bool] = can_edit_tag
+ self.can_react_to_messages: Optional[bool] = can_react_to_messages
# noinspection PyUnresolvedReferences
@@ -3861,6 +3903,9 @@ class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable):
messages
:type can_add_web_page_previews: :obj:`bool`
+ :param can_react_to_messages: Optional. True, if the user is allowed to react to messages. If omitted, defaults to the value of can_send_messages.
+ :type can_react_to_messages: :obj:`bool`
+
:param can_change_info: Optional. True, if the user is allowed to change the chat title, photo and other settings.
Ignored in public supergroups
:type can_change_info: :obj:`bool`
@@ -3896,7 +3941,7 @@ def __init__(self, can_send_messages=None, can_send_media_messages=None,can_send
can_send_voice_notes=None, can_send_polls=None, can_send_other_messages=None,
can_add_web_page_previews=None, can_change_info=None,
can_invite_users=None, can_pin_messages=None,
- can_manage_topics=None, can_edit_tag=None, **kwargs):
+ can_manage_topics=None, can_edit_tag=None, can_react_to_messages=None, **kwargs):
self.can_send_messages: Optional[bool] = can_send_messages
self.can_send_polls: Optional[bool] = can_send_polls
self.can_send_other_messages: Optional[bool] = can_send_other_messages
@@ -3912,6 +3957,7 @@ def __init__(self, can_send_messages=None, can_send_media_messages=None,can_send
self.can_send_videos: Optional[bool] = can_send_videos
self.can_send_video_notes: Optional[bool] = can_send_video_notes
self.can_send_voice_notes: Optional[bool] = can_send_voice_notes
+ self.can_react_to_messages: Optional[bool] = can_react_to_messages
if kwargs.get("de_json", False) and can_send_media_messages is not None:
# Telegram passes can_send_media_messages in Chat.permissions. Temporary created parameter "de_json" allows avoid
@@ -3961,6 +4007,8 @@ def to_dict(self):
json_dict['can_manage_topics'] = self.can_manage_topics
if self.can_edit_tag is not None:
json_dict['can_edit_tag'] = self.can_edit_tag
+ if self.can_react_to_messages is not None:
+ json_dict['can_react_to_messages'] = self.can_react_to_messages
return json_dict
@@ -7438,6 +7486,180 @@ def to_dict(self):
return ret
+class InputMediaLivePhoto(InputMedia):
+ """
+ Represents a live photo to be sent.
+
+ Telegram Documentation: https://core.telegram.org/bots/api#inputmedialivephoto
+
+ :param media: Video of the live photo to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or pass “attach://”
+ to upload a new one using multipart/form-data under name. More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type media: :obj:`str`
+
+ :param photo: The static photo to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or pass “attach://” to upload a new one
+ using multipart/form-data under name. More information on Sending Files ». Sending live photos by a URL is currently unsupported.
+ :type photo: :obj:`str`
+
+ :param caption: Optional. Caption of the live photo to be sent, 0-1024 characters after entities parsing
+ :type caption: :obj:`str`
+
+ :param parse_mode: Optional. Mode for parsing entities in the live photo 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:`telebot.types.MessageEntity`
+
+ :param show_caption_above_media: Optional. Pass True, if the caption must be shown above the message media
+ :type show_caption_above_media: :obj:`bool`
+
+ :param has_spoiler: Optional. Pass True if the live photo needs to be covered with a spoiler animation
+ :type has_spoiler: :obj:`bool`
+
+ :return: Instance of the class
+ :rtype: :class:`telebot.types.InputMediaLivePhoto`
+ """
+ def __init__(self, media: str, photo: str, caption: Optional[str] = None, parse_mode: Optional[str] = None,
+ caption_entities: Optional[List[MessageEntity]] = None, show_caption_above_media: Optional[bool] = None, has_spoiler: Optional[bool] = None):
+ super(InputMediaLivePhoto, self).__init__(type="live_photo", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities)
+ self.photo: str = photo
+ self.show_caption_above_media: Optional[bool] = show_caption_above_media
+ self.has_spoiler: Optional[bool] = has_spoiler
+
+ def to_dict(self):
+ json_dict = super(InputMediaLivePhoto, self).to_dict()
+ json_dict['photo'] = self.photo
+ if self.show_caption_above_media is not None:
+ json_dict['show_caption_above_media'] = self.show_caption_above_media
+ if self.has_spoiler is not None:
+ json_dict['has_spoiler'] = self.has_spoiler
+ return json_dict
+
+class InputMediaLocation(InputMedia):
+ """
+ Represents a location to be sent.
+
+ Telegram Documentation: https://core.telegram.org/bots/api#inputmedialocation
+
+ :param latitude: Latitude of the location
+ :type latitude: :obj:`float`
+
+ :param longitude: Longitude of the location
+ :type longitude: :obj:`float`
+
+ :param horizontal_accuracy: Optional. The radius of uncertainty for the location, measured in meters; 0-1500
+ :type horizontal_accuracy: :obj:`float`
+
+ :return: Instance of the class
+ :rtype: :class:`telebot.types.InputMediaLocation`
+ """
+ def __init__(self, latitude: float, longitude: float, horizontal_accuracy: Optional[float] = None):
+ super(InputMediaLocation, self).__init__(type="location", media="")
+ self.latitude: float = latitude
+ self.longitude: float = longitude
+ self.horizontal_accuracy: Optional[float] = horizontal_accuracy
+
+ def to_dict(self):
+ json_dict = super(InputMediaLocation, self).to_dict()
+ json_dict['latitude'] = self.latitude
+ json_dict['longitude'] = self.longitude
+ if self.horizontal_accuracy is not None:
+ json_dict['horizontal_accuracy'] = self.horizontal_accuracy
+ # Remove 'media' field as it's not used for location
+ json_dict.pop('media', None)
+ return json_dict
+
+class InputMediaSticker(InputMedia):
+ """
+ Represents a sticker file to be sent.
+
+ Telegram Documentation: https://core.telegram.org/bots/api#inputmediasticker
+
+ :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 .WEBP sticker from the Internet, or pass “attach://”
+ to upload a new .WEBP, .TGS, or .WEBM sticker using multipart/form-data under name. More information
+ on Sending Files »
+ :type media: :obj:`str`
+
+ :param emoji: Optional. Emoji associated with the sticker; only for just uploaded stickers
+ :type emoji: :obj:`str`
+
+ :return: Instance of the class
+ :rtype: :class:`telebot.types.InputMediaSticker`
+ """
+ def __init__(self, media: str, emoji: Optional[str] = None):
+ super(InputMediaSticker, self).__init__(type="sticker", media=media)
+ self.emoji: Optional[str] = emoji
+
+ def to_dict(self):
+ json_dict = super(InputMediaSticker, self).to_dict()
+ if self.emoji:
+ json_dict['emoji'] = self.emoji
+ return json_dict
+
+class InputMediaVenue(InputMedia):
+ """
+ Represents a venue to be sent.
+
+ Telegram Documentation: https://core.telegram.org/bots/api#inputmediavenue
+
+ :param latitude: Latitude of the location
+ :type latitude: :obj:`float`
+
+ :param longitude: Longitude of the location
+ :type longitude: :obj:`float`
+
+ :param title: Name of the venue
+ :type title: :obj:`str`
+
+ :param address: Address of the venue
+ :type address: :obj:`str`
+
+ :param foursquare_id: Optional. Foursquare identifier of the venue
+ :type foursquare_id: :obj:`str`
+
+ :param foursquare_type: Optional. Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.)
+ :type foursquare_type: :obj:`str`
+
+ :param google_place_id: Optional. Google Places identifier of the venue
+ :type google_place_id: :obj:`str`
+
+ :param google_place_type: Optional. Google Places type of the venue. (See supported types.)
+ :type google_place_type: :obj:`str`
+
+ :return: Instance of the class
+ :rtype: :class:`telebot.types.InputMediaVenue`
+ """
+ def __init__(self, latitude: float, longitude: float, title: str, address: str, foursquare_id: Optional[str] = None,
+ foursquare_type: Optional[str] = None, google_place_id: Optional[str] = None, google_place_type: Optional[str] = None):
+ super(InputMediaVenue, self).__init__(type="venue", media="")
+ self.latitude: float = latitude
+ self.longitude: float = longitude
+ self.title: str = title
+ self.address: str = address
+ self.foursquare_id: Optional[str] = foursquare_id
+ self.foursquare_type: Optional[str] = foursquare_type
+ self.google_place_id: Optional[str] = google_place_id
+ self.google_place_type: Optional[str] = google_place_type
+
+ def to_dict(self):
+ json_dict = super(InputMediaVenue, self).to_dict()
+ json_dict['latitude'] = self.latitude
+ json_dict['longitude'] = self.longitude
+ json_dict['title'] = self.title
+ json_dict['address'] = self.address
+ if self.foursquare_id:
+ json_dict['foursquare_id'] = self.foursquare_id
+ if self.foursquare_type:
+ json_dict['foursquare_type'] = self.foursquare_type
+ if self.google_place_id:
+ json_dict['google_place_id'] = self.google_place_id
+ if self.google_place_type:
+ json_dict['google_place_type'] = self.google_place_type
+ # Remove 'media' field as it's not used for venue
+ json_dict.pop('media', None)
+ return json_dict
+
+
class PollOption(JsonDeserializable):
"""
This object contains information about one answer option in a poll.
@@ -7453,6 +7675,9 @@ class PollOption(JsonDeserializable):
:param text_entities: Optional. Special entities that appear in the option text. Currently, only custom emoji entities are allowed in poll option texts
:type text_entities: :obj:`list` of :class:`telebot.types.MessageEntity`
+ :param media: Optional. Media added to the poll option
+ :type media: :class:`telebot.types.PollMedia`
+
:param voter_count: Number of users that voted for this option
:type voter_count: :obj:`int`
@@ -7478,9 +7703,11 @@ def de_json(cls, json_string):
obj['added_by_user'] = User.de_json(obj['added_by_user'])
if 'added_by_chat' in obj:
obj['added_by_chat'] = Chat.de_json(obj['added_by_chat'])
+ if 'media' in obj:
+ obj['media'] = PollMedia.de_json(obj['media'])
return cls(**obj)
- def __init__(self, text, persistent_id, voter_count = 0, text_entities=None, added_by_user=None, added_by_chat=None, addition_date=None, **kwargs):
+ def __init__(self, text, persistent_id, voter_count = 0, text_entities=None, added_by_user=None, added_by_chat=None, addition_date=None, media=None, **kwargs):
self.text: str = text
self.persistent_id: str = persistent_id
self.voter_count: int = voter_count
@@ -7488,6 +7715,7 @@ def __init__(self, text, persistent_id, voter_count = 0, text_entities=None, add
self.added_by_user: Optional[User] = added_by_user
self.added_by_chat: Optional[Chat] = added_by_chat
self.addition_date: Optional[int] = addition_date
+ self.media: Optional[PollMedia] = media
# Converted in _convert_poll_options
# def to_json(self):
# # send_poll Option is a simple string: https://core.telegram.org/bots/api#sendpoll
@@ -7509,14 +7737,18 @@ class InputPollOption(JsonSerializable):
:param text_entities: Optional. A JSON-serialized list of special entities that appear in the poll option text. It can be specified instead of text_parse_mode
:type text_entities: :obj:`list` of :class:`telebot.types.MessageEntity`
+ :param media: Optional. Media added to the poll option
+ :type media: :class:`telebot.types.PollMedia`
+
:return: Instance of the class
:rtype: :class:`telebot.types.PollOption`
"""
def __init__(self, text: str, text_parse_mode: Optional[str] = None, text_entities: Optional[List[MessageEntity]] = None,
- **kwargs):
+ media: Optional[PollMedia] = None, **kwargs):
self.text: str = text
self.text_parse_mode: Optional[str] = text_parse_mode
self.text_entities: Optional[List[MessageEntity]] = text_entities
+ self.media: Optional[PollMedia] = media
def to_json(self):
return json.dumps(self.to_dict())
@@ -7529,6 +7761,8 @@ def to_dict(self):
json_dict["text_parse_mode"] = self.text_parse_mode
if self.text_entities:
json_dict['text_entities'] = [entity.to_dict() for entity in self.text_entities]
+ if self.media:
+ json_dict['media'] = self.media.to_dict()
return json_dict
@@ -7545,7 +7779,7 @@ class Poll(JsonDeserializable):
:param question: Poll question, 1-300 characters
:type question: :obj:`str`
- :param options: List of poll options
+ :param options: A JSON-serialized list of 1-12 answer options
:type options: :obj:`list` of :class:`telebot.types.PollOption`
:param total_voter_count: Total number of users that voted in the poll
@@ -7575,6 +7809,9 @@ class Poll(JsonDeserializable):
:param explanation_entities: Optional. Special entities like usernames, URLs, bot commands, etc. that appear in the explanation
:type explanation_entities: :obj:`list` of :class:`telebot.types.MessageEntity`
+ :param explanation_media: Optional. Media added to the quiz explanation
+ :type explanation_media: :class:`telebot.types.PollMedia`
+
:param open_period: Optional. Amount of time in seconds the poll will be active after creation
:type open_period: :obj:`int`
@@ -7587,12 +7824,21 @@ class Poll(JsonDeserializable):
:param allows_revoting: True, if the poll allows to change the chosen answer options
:type allows_revoting: :obj:`bool`
+ :param members_only: True if voting is limited to users who have been members of the chat where the poll was originally sent for more than 24 hours
+ :type members_only: :obj:`bool`
+
+ :param country_codes: Optional. A list of two-letter ISO 3166-1 alpha-2 country codes indicating the countries from which users can vote in the poll. If omitted, then users from any country can participate in the poll.
+ :type country_codes: :obj:`list` of :obj:`str`
+
:param description: Optional. Description of the poll; for polls inside the Message object only
:type description: :obj:`str`
:param description_entities: Optional. Special entities like usernames, URLs, bot commands, etc. that appear in the description
:type description_entities: :obj:`list` of :class:`telebot.types.MessageEntity`
+ :param media: Optional. Media added to the poll description; for polls inside the Message object only
+ :type media: :class:`telebot.types.PollMedia`
+
:return: Instance of the class
:rtype: :class:`telebot.types.Poll`
"""
@@ -7611,6 +7857,11 @@ def de_json(cls, json_string):
obj['question_entities'] = Message.parse_entities(obj['question_entities'])
if 'description_entities' in obj:
obj['description_entities'] = Message.parse_entities(obj['description_entities'])
+ if 'media' in obj:
+ obj['media'] = PollMedia.de_json(obj['media'])
+ if 'explanation_media' in obj:
+ obj['explanation_media'] = PollMedia.de_json(obj['explanation_media'])
+
return cls(**obj)
def __init__(
@@ -7622,7 +7873,7 @@ def __init__(
close_date: int = None, poll_type: str = None, question_entities: List[MessageEntity] = None,
correct_option_ids: List[int] = None, allows_revoting: bool = None,
description: str = None, description_entities: List[MessageEntity] = None,
- **kwargs):
+ media: PollMedia = None, members_only: bool = None, country_codes: List[str] = None, explanation_media: PollMedia = None, **kwargs):
self.id: str = poll_id
self.question: str = question
self.options: List[PollOption] = options
@@ -7644,6 +7895,10 @@ def __init__(
self.allows_revoting: bool = allows_revoting
self.description: str = description
self.description_entities: List[MessageEntity] = description_entities
+ self.media: PollMedia = media
+ self.explanation_media: PollMedia = explanation_media
+ self.members_only: bool = members_only
+ self.country_codes: List[str] = country_codes
@property
def correct_option_id(self) -> Optional[int]:
@@ -9122,6 +9377,9 @@ class ExternalReplyInfo(JsonDeserializable):
:param document: Optional. Message is a general file, information about the file
:type document: :class:`Document`
+ :param live_photo: Optional. Message is a live photo, information about the live photo
+ :type live_photo: :class:`LivePhoto`
+
:param paid_media: Optional. Message is a paid media content
:type paid_media: :class:`PaidMedia`
@@ -9228,6 +9486,8 @@ def de_json(cls, json_string):
obj['paid_media'] = PaidMediaInfo.de_json(obj['paid_media'])
if 'checklist' in obj:
obj['checklist'] = Checklist.de_json(obj['checklist'])
+ if 'live_photo' in obj:
+ obj['live_photo'] = LivePhoto.de_json(obj['live_photo'])
return cls(**obj)
def __init__(
@@ -9241,7 +9501,7 @@ def __init__(
giveaway_winners: Optional[GiveawayWinners]=None, invoice: Optional[Invoice]=None,
location: Optional[Location]=None, poll: Optional[Poll]=None,
venue: Optional[Venue]=None, paid_media: Optional[PaidMediaInfo]=None,
- checklist: Optional[Checklist]=None, **kwargs) -> None:
+ live_photo: Optional[LivePhoto]=None, checklist: Optional[Checklist]=None, **kwargs) -> None:
self.origin: MessageOrigin = origin
self.chat: Optional[Chat] = chat
self.message_id: Optional[int] = message_id
@@ -9266,6 +9526,7 @@ def __init__(
self.poll: Optional[Poll] = poll
self.venue: Optional[Venue] = venue
self.paid_media: Optional[PaidMediaInfo] = paid_media
+ self.live_photo: Optional[LivePhoto] = live_photo
self.checklist: Optional[Checklist] = checklist
@@ -11164,6 +11425,7 @@ class PaidMedia(JsonDeserializable, ABC):
PaidMediaPreview
PaidMediaPhoto
PaidMediaVideo
+ PaidMediaLivePhoto
Telegram documentation: https://core.telegram.org/bots/api#paidmedia
@@ -11221,6 +11483,34 @@ def de_json(cls, json_string):
return cls(**obj)
+class PaidMediaLivePhoto(PaidMedia):
+ """
+ The paid media is a live photo.
+
+ Telegram documentation: https://core.telegram.org/bots/api#paidmedialivephoto
+
+ :param type: Type of the paid media, always “live_photo”
+ :type type: :obj:`str`
+
+ :param live_photo: The photo
+ :type live_photo: :class:`LivePhoto`
+
+ :return: Instance of the class
+ :rtype: :class:`PaidMediaLivePhoto`
+ """
+
+ def __init__(self, type, live_photo, **kwargs):
+ self.type: str = type
+ self.live_photo: LivePhoto = live_photo
+
+ @classmethod
+ def de_json(cls, json_string):
+ if json_string is None: return None
+ obj = cls.check_json(json_string)
+ obj['live_photo'] = LivePhoto.de_json(obj['live_photo'])
+ return cls(**obj)
+
+
# noinspection PyShadowingBuiltins
class PaidMediaPhoto(PaidMedia):
"""
@@ -11315,6 +11605,7 @@ class InputPaidMedia(Dictionaryable, JsonSerializable):
This object describes the paid media to be sent. Currently, it can be one of
InputPaidMediaPhoto
InputPaidMediaVideo
+ InputPaidMediaLivePhoto
Telegram documentation: https://core.telegram.org/bots/api#inputpaidmedia
@@ -11369,6 +11660,37 @@ class InputPaidMediaPhoto(InputPaidMedia):
def __init__(self, media: Union[str, InputFile], **kwargs):
super().__init__(type='photo', media=media)
+class InputPaidMediaLivePhoto(InputPaidMedia):
+ """
+ The paid media to send is a live photo.
+
+ Telegram documentation: https://core.telegram.org/bots/api#inputpaidmedialivephoto
+
+ :param type: Type of the media, must be live_photo
+ :type type: :obj:`str`
+
+ :param media: Video of the live photo to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or
+ pass “attach://” to upload a new one using multipart/form-data under name. More information on Sending Files ».
+ Sending live photos by a URL is currently unsupported.
+ :type media: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :param photo: The static photo to send. Pass a file_id to send a file that exists on the Telegram servers (recommended) or
+ pass “attach://” to upload a new one using multipart/form-data under name. More information on Sending Files ».
+ Sending live photos by a URL is currently unsupported.
+ :type photo: :obj:`str` or :class:`telebot.types.InputFile`
+
+ :return: Instance of the class
+ :rtype: :class:`InputPaidMediaLivePhoto`
+ """
+ def __init__(self, media: Union[str, InputFile], photo: Union[str, InputFile], **kwargs):
+ super().__init__(type='live_photo', media=media)
+ self.photo: Union[str, InputFile] = photo
+
+ def to_dict(self):
+ data = super().to_dict()
+ data['photo'] = self.photo if service_utils.is_string(self.photo) else 'attach://{0}'.format(service_utils.generate_random_token())
+ return data
+
class InputPaidMediaVideo(InputPaidMedia):
"""
@@ -14016,3 +14338,187 @@ def de_json(cls, json_string):
if 'option_text_entities' in obj:
obj['option_text_entities'] = [MessageEntity.de_json(entity) for entity in obj['option_text_entities']]
return cls(**obj)
+
+
+class SentGuestMessage(JsonDeserializable):
+ """
+ Describes an inline message sent by a guest bot.
+
+ Telegram documentation: https://core.telegram.org/bots/api#sentguestmessage
+
+ :param inline_message_id: Identifier of the sent inline message
+ :type inline_message_id: :obj:`str`
+
+ :return: Instance of the class
+ :rtype: :class:`SentGuestMessage`
+ """
+ def __init__(self, inline_message_id: str, **kwargs):
+ self.inline_message_id: str = inline_message_id
+
+ @classmethod
+ def de_json(cls, json_string):
+ if json_string is None: return None
+ obj = cls.check_json(json_string)
+ return cls(**obj)
+
+
+class PollMedia(JsonDeserializable):
+ """
+ This object describes the media attached to a poll option. At most one of the optional fields can be present in any given object.
+
+ Telegram documentation: https://core.telegram.org/bots/api#pollmedia
+
+ :param animation: Optional. Media is an animation, information about the animation
+ :type animation: :class:`Animation`
+
+ :param audio: Optional. Media is an audio file, information about the file; currently, can't be received in a poll option
+ :type audio: :class:`Audio`
+
+ :param document: Optional. Media is a general file, information about the file; currently, can't be received in a poll option
+ :type document: :class:`Document`
+
+ :param live_photo: Optional. Media is a live photo, information about the live photo
+ :type live_photo: :class:`LivePhoto`
+
+ :param location: Optional. Media is a shared location, information about the location
+ :type location: :class:`Location`
+
+ :param photo: Optional. Media is a photo, available sizes of the photo
+ :type photo: :obj:`list` of :class:`PhotoSize`
+
+ :param sticker: Optional. Media is a sticker, information about the sticker; currently, for poll options only
+ :type sticker: :class:`Sticker`
+
+ :param venue: Optional. Media is a venue, information about the venue
+ :type venue: :class:`Venue`
+
+ :param video: Optional. Media is a video, information about the video
+ :type video: :class:`Video`
+
+ :return: Instance of the class
+ :rtype: :class:`PollMedia`
+
+ """
+ def __init__(self, animation: Optional[Animation] = None, audio: Optional[Audio] = None, document: Optional[Document] = None,
+ live_photo: Optional[LivePhoto] = None, location: Optional[Location] = None, photo: Optional[List[PhotoSize]] = None,
+ sticker: Optional[Sticker] = None, venue: Optional[Venue] = None, video: Optional[Video] = None, **kwargs):
+ self.animation: Optional[Animation] = animation
+ self.audio: Optional[Audio] = audio
+ self.document: Optional[Document] = document
+ self.live_photo: Optional[LivePhoto] = live_photo
+ self.location: Optional[Location] = location
+ self.photo: Optional[List[PhotoSize]] = photo
+ self.sticker: Optional[Sticker] = sticker
+ self.venue: Optional[Venue] = venue
+ self.video: Optional[Video] = video
+
+ @classmethod
+ def de_json(cls, json_string):
+ if json_string is None: return None
+ obj = cls.check_json(json_string)
+ if 'animation' in obj:
+ obj['animation'] = Animation.de_json(obj['animation'])
+ if 'audio' in obj:
+ obj['audio'] = Audio.de_json(obj['audio'])
+ if 'document' in obj:
+ obj['document'] = Document.de_json(obj['document'])
+ if 'live_photo' in obj:
+ obj['live_photo'] = LivePhoto.de_json(obj['live_photo'])
+ if 'location' in obj:
+ obj['location'] = Location.de_json(obj['location'])
+ if 'photo' in obj:
+ obj['photo'] = [PhotoSize.de_json(photo) for photo in obj['photo']]
+ if 'sticker' in obj:
+ obj['sticker'] = Sticker.de_json(obj['sticker'])
+ if 'venue' in obj:
+ obj['venue'] = Venue.de_json(obj['venue'])
+ if 'video' in obj:
+ obj['video'] = Video.de_json(obj['video'])
+ return cls(**obj)
+
+# why not..
+InputPollMedia = Union[InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaLivePhoto, InputMediaLocation, InputMediaPhoto, InputMediaVenue, InputMediaVideo]
+
+InputPollOptionMedia = Union[InputMediaAnimation, InputMediaLivePhoto, InputMediaLocation, InputMediaPhoto, InputMediaSticker, InputMediaVenue, InputMediaVideo]
+
+
+class LivePhoto(JsonDeserializable):
+ """
+ This object represents a live photo.
+
+ Telegram documentation: https://core.telegram.org/bots/api#livephoto
+
+ :param photo: Optional. Available sizes of the corresponding static photo
+ :type photo: :obj:`list` of :class:`PhotoSize`
+
+ :param file_id: Identifier for the video file which can be used to download or reuse the file
+ :type file_id: :obj:`str`
+
+ :param file_unique_id: Unique identifier for the video file which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.
+ :type file_unique_id: :obj:`str`
+
+ :param width: Video width as defined by the sender
+ :type width: :obj:`int`
+
+ :param height: Video height as defined by the sender
+ :type height: :obj:`int`
+
+ :param duration: Duration of the video in seconds as defined by the sender
+ :type duration: :obj:`int`
+
+ :param mime_type: Optional. MIME type of the file as defined by the sender
+ :type mime_type: :obj:`str`
+
+ :param file_size: Optional. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.
+ :type file_size: :obj:`int`
+
+ :return: Instance of the class
+ :rtype: :class:`LivePhoto`
+ """
+ def __init__(self, file_id: str, file_unique_id: str, width: int, height: int, duration: int, photo: Optional[List[PhotoSize]] = None,
+ mime_type: Optional[str] = None, file_size: Optional[int] = None, **kwargs):
+ self.photo: Optional[List[PhotoSize]] = photo
+ self.file_id: str = file_id
+ self.file_unique_id: str = file_unique_id
+ self.width: int = width
+ self.height: int = height
+ self.duration: int = duration
+ self.mime_type: Optional[str] = mime_type
+ self.file_size: Optional[int] = file_size
+
+ @classmethod
+ def de_json(cls, json_string):
+ if json_string is None: return None
+ obj = cls.check_json(json_string)
+ if 'photo' in obj:
+ obj['photo'] = [PhotoSize.de_json(photo) for photo in obj['photo']]
+ return cls(**obj)
+
+
+class BotAccessSettings(JsonDeserializable):
+ """
+ This object describes the access settings of a bot.
+
+ Telegram documentation: https://core.telegram.org/bots/api#botaccesssettings
+
+ :param is_access_restricted: True, if only selected users can access the bot. The bot's owner can always access it.
+ :type is_access_restricted: :obj:`bool`
+
+ :param added_users: Optional. The list of other users who have access to the bot if the access is restricted
+ :type added_users: :obj:`list` of :class:`User`
+
+ :return: Instance of the class
+ :rtype: :class:`BotAccessSettings`
+ """
+ def __init__(self, is_access_restricted: bool, added_users: Optional[List[User]] = None, **kwargs):
+ self.is_access_restricted: bool = is_access_restricted
+ self.added_users: Optional[List[User]] = added_users
+
+ @classmethod
+ def de_json(cls, json_string):
+ if json_string is None: return None
+ obj = cls.check_json(json_string)
+ if 'added_users' in obj:
+ obj['added_users'] = [User.de_json(user) for user in obj['added_users']]
+ return cls(**obj)
+
\ No newline at end of file
diff --git a/telebot/util.py b/telebot/util.py
index ffadfe8f5..4461929ce 100644
--- a/telebot/util.py
+++ b/telebot/util.py
@@ -53,7 +53,7 @@
"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",
+ "purchased_paid_media", "managed_bot", "guest_message"
]
diff --git a/tests/test_handler_backends.py b/tests/test_handler_backends.py
index 3ef28c15a..4981d9457 100644
--- a/tests/test_handler_backends.py
+++ b/tests/test_handler_backends.py
@@ -71,7 +71,7 @@ def update_type(message):
chat_boost_removed = None
return types.Update(1001234038283, 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, chat_boost, chat_boost_removed, None, None, None, None, None, None)
+ my_chat_member, chat_member, chat_join_request, message_reaction, message_reaction_count, chat_boost, chat_boost_removed, None, None, None, None, None, None, None)
@pytest.fixture()
@@ -95,7 +95,7 @@ def reply_to_message_update_type(reply_to_message):
chat_boost_removed = None
return types.Update(1001234038284, reply_to_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, chat_boost, chat_boost_removed, None, None, None, None, None, None)
+ poll, poll_answer, my_chat_member, chat_member, chat_join_request, message_reaction, message_reaction_count, chat_boost, chat_boost_removed, None, None, None, None, None, None, None)
def next_handler(message):
diff --git a/tests/test_telebot.py b/tests/test_telebot.py
index 9c7ed221f..e66a16f1f 100644
--- a/tests/test_telebot.py
+++ b/tests/test_telebot.py
@@ -555,7 +555,8 @@ def create_message_update(text):
business_connection=None,
edited_business_message=None,
deleted_business_messages=None,
- managed_bot=None)
+ managed_bot=None,
+ guest_message=message)
def test_is_string_unicode(self):
s1 = u'string'