From 9d701bc226d67b7eba64cf681e7636aa8c616cc4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 20:15:25 +0100 Subject: [PATCH 1/4] Fix import order causing errors --- pyrogram/api/errors/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/api/errors/__init__.py b/pyrogram/api/errors/__init__.py index 8a1dc699..ca65619c 100644 --- a/pyrogram/api/errors/__init__.py +++ b/pyrogram/api/errors/__init__.py @@ -16,5 +16,5 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .error import UnknownError from .exceptions import * +from .error import UnknownError From 5aa93b8287ca4fbe1de306296d47dd43f95abf5f Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Wed, 20 Mar 2019 16:20:38 +0300 Subject: [PATCH 2/4] Show TOS on signup just once while program is running (#231) --- pyrogram/client/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 253cc754..737139ca 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -181,6 +181,8 @@ class Client(Methods, BaseClient): Defaults to False (normal session). """ + terms_of_service_displayed = False + def __init__(self, session_name: str, api_id: Union[int, str] = None, @@ -591,8 +593,9 @@ class Client(Methods, BaseClient): phone_code_hash = r.phone_code_hash terms_of_service = r.terms_of_service - if terms_of_service: + if terms_of_service and not Client.terms_of_service_displayed: print("\n" + terms_of_service.text + "\n") + Client.terms_of_service_displayed = True if self.force_sms: self.send( From ac591cf3c7e6f7da20d957d6f3f911dad6282cab Mon Sep 17 00:00:00 2001 From: Eric Solinas Date: Wed, 20 Mar 2019 15:44:20 +0100 Subject: [PATCH 3/4] Add more Message convenience methods (#233) * Added convenience methods message.reply_animation message.reply_audio message.reply_cached_media message.reply_chat_action message.reply_contact message.reply_document message.reply_game message.reply_inline_bot_result message.reply_location message.reply_media_group message.reply_photo message.reply_poll message.reply_sticker message.reply_venue message.reply_video message.reply_video_note message.reply_voice message.edit_caption message.edit_media message.edit_reply_markup message.pin fixed send_document docstrings while doing so uniformed function declaration of send_poll * Update style and small fixes --- .../client/methods/messages/send_document.py | 2 +- pyrogram/client/methods/messages/send_poll.py | 28 +- .../types/messages_and_media/message.py | 1855 ++++++++++++++++- 3 files changed, 1854 insertions(+), 31 deletions(-) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 2693697a..343a63a3 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -60,7 +60,7 @@ class SendDocument(BaseClient): pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. - thumb (``str``): + thumb (``str``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 90 pixels. diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 423f962d..c9525c83 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,20 +24,20 @@ from pyrogram.client.ext import BaseClient class SendPoll(BaseClient): - def send_poll \ - (self, - chat_id: Union[int, str], - question: str, - options: List[str], - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None - ) -> "pyrogram.Message": + def send_poll( + self, + chat_id: Union[int, str], + question: str, + options: List[str], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send a new poll. Args: diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 9a4e4314..558efd38 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -21,6 +21,8 @@ from typing import List, Match, Union import pyrogram from pyrogram.api import types from pyrogram.api.errors import MessageIdsEmpty +from pyrogram.client.ext import ChatAction +from pyrogram.client.types.input_media import InputMedia from .contact import Contact from .location import Location from .message_entity import MessageEntity @@ -611,14 +613,16 @@ class Message(PyrogramType, Update): return parsed_message - def reply(self, - text: str, - quote: bool = None, - parse_mode: str = "", - disable_web_page_preview: bool = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + def reply( + self, + text: str, + quote: bool = None, + parse_mode: str = "", + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None + ) -> "Message": """Bound method *reply* of :obj:`Message `. Use as a shortcut for: @@ -686,7 +690,1661 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def edit(self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup=None): + def reply_animation( + self, + animation: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + reply_to_message_id: int = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_animation* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_animation( + chat_id=message.chat.id, + animation=animation + ) + + Example: + .. code-block:: python + + message.reply_animation(animation) + + Args: + animation (``str``): + Animation to send. + Pass a file_id as string to send an animation that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an animation from the Internet, or + pass a file path as string to upload a new animation that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Animation caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of sent animation in seconds. + + width (``int``, *optional*): + Animation width. + + height (``int``, *optional*): + Animation height. + + thumb (``str``, *optional*): + Thumbnail of the animation file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_animation( + chat_id=self.chat.id, + animation=animation, + caption=caption, + parse_mode=parse_mode, + duration=duration, + width=width, + height=height, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_audio( + self, + audio: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: str = None, + title: str = None, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_audio* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_audio( + chat_id=message.chat.id, + audio=audio + ) + + Example: + .. code-block:: python + + message.reply_audio(audio) + + Args: + audio (``str``): + Audio file to send. + Pass a file_id as string to send an audio file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or + pass a file path as string to upload a new audio file that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Audio caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the audio in seconds. + + performer (``str``, *optional*): + Performer. + + title (``str``, *optional*): + Track name. + + thumb (``str``, *optional*): + Thumbnail of the music file album cover. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_audio( + chat_id=self.chat.id, + audio=audio, + caption=caption, + parse_mode=parse_mode, + duration=duration, + performer=performer, + title=title, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_cached_media( + self, + file_id: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_cached_media* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_cached_media( + chat_id=message.chat.id, + file_id=file_id + ) + + Example: + .. code-block:: python + + message.reply_cached_media(file_id) + + Args: + file_id (``str``): + Media to send. + Pass a file_id as string to send a media that exists on the Telegram servers. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``bool``, *optional*): + Media caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_cached_media( + chat_id=self.chat.id, + file_id=file_id, + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_chat_action( + self, + action: Union[ChatAction, str], + progress: int = 0 + ) -> "Message": + """Bound method *reply_chat_action* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_chat_action( + chat_id=message.chat.id, + action="typing" + ) + + Example: + .. code-block:: python + + message.reply_chat_action("typing") + + Args: + action (:obj:`ChatAction ` | ``str``): + Type of action to broadcast. + Choose one from the :class:`ChatAction ` enumeration, + depending on what the user is about to receive. + You can also provide a string (e.g. "typing", "upload_photo", "record_audio", ...). + + progress (``int``, *optional*): + Progress of the upload process. + Currently useless because official clients don't seem to be handling this. + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` if the provided string is not a valid ChatAction. + """ + return self._client.send_chat_action( + chat_id=self.chat.id, + action=action, + progress=progress + ) + + def reply_contact( + self, + phone_number: str, + first_name: str, + quote: bool = None, + last_name: str = "", + vcard: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_contact* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_contact( + chat_id=message.chat.id, + phone_number=phone_number, + first_name=first_name + ) + + Example: + .. code-block:: python + + message.reply_contact(phone_number, "Dan") + + Args: + phone_number (``str``): + Contact's phone number. + + first_name (``str``): + Contact's first name. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + last_name (``str``, *optional*): + Contact's last name. + + vcard (``str``, *optional*): + Additional data about the contact in the form of a vCard, 0-2048 bytes + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_contact( + chat_id=self.chat.id, + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + vcard=vcard, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_document( + self, + document: str, + quote: bool = None, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_document* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_document( + chat_id=message.chat.id, + document=document + ) + + Example: + .. code-block:: python + + message.reply_document(document) + + Args: + document (``str``): + File to send. + Pass a file_id as string to send a file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a file from the Internet, or + pass a file path as string to upload a new file that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + thumb (``str``, *optional*): + Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + caption (``str``, *optional*): + Document caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_document( + chat_id=self.chat.id, + document=document, + thumb=thumb, + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_game( + self, + game_short_name: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_game* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_game( + chat_id=message.chat.id, + game_short_name="lumberjack" + ) + + Example: + .. code-block:: python + + message.reply_game("lumberjack") + + Args: + game_short_name (``str``): + Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. + If not empty, the first button must launch the game. + + Returns: + On success, the sent :obj:`Message` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_game( + chat_id=self.chat.id, + game_short_name=game_short_name, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_inline_bot_result( + self, + query_id: int, + result_id: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + hide_via: bool = None + ) -> "Message": + """Bound method *reply_inline_bot_result* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_inline_bot_result( + chat_id=message.chat.id, + query_id=query_id, + result_id=result_id + ) + + Example: + .. code-block:: python + + message.reply_inline_bot_result(query_id, result_id) + + Args: + query_id (``int``): + Unique identifier for the answered query. + + result_id (``str``): + Unique identifier for the result that was chosen. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``bool``, *optional*): + If the message is a reply, ID of the original message. + + hide_via (``bool``): + Sends the message with *via @bot* hidden. + + Returns: + On success, the sent Message is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_inline_bot_result( + chat_id=self.chat.id, + query_id=query_id, + result_id=result_id, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + hide_via=hide_via + ) + + def reply_location( + self, + latitude: float, + longitude: float, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_location* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_location( + chat_id=message.chat.id, + latitude=41.890251, + longitude=12.492373 + ) + + Example: + .. code-block:: python + + message.reply_location(41.890251, 12.492373) + + Args: + latitude (``float``): + Latitude of the location. + + longitude (``float``): + Longitude of the location. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_location( + chat_id=self.chat.id, + latitude=latitude, + longitude=longitude, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_media_group( + self, + media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None + ) -> "Message": + """Bound method *reply_media_group* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_media_group( + chat_id=message.chat.id, + media=list_of_media + ) + + Example: + .. code-block:: python + + message.reply_media_group(list_of_media) + + Args: + media (``list``): + A list containing either :obj:`InputMediaPhoto ` or + :obj:`InputMediaVideo ` objects + describing photos and videos to be sent, must include 2–10 items. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + Returns: + On success, a :obj:`Messages ` object is returned containing all the + single messages sent. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_media_group( + chat_id=self.chat.id, + media=media, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id + ) + + def reply_photo( + self, + photo: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + ttl_seconds: int = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_photo* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_photo( + chat_id=message.chat.id, + photo=photo + ) + + Example: + .. code-block:: python + + message.reply_photo(photo) + + Args: + photo (``str``): + Photo to send. + Pass a file_id as string to send a photo that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a photo from the Internet, or + pass a file path as string to upload a new photo that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``bool``, *optional*): + Photo caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + ttl_seconds (``int``, *optional*): + Self-Destruct Timer. + If you set a timer, the photo will self-destruct in *ttl_seconds* + seconds after it was viewed. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_photo( + chat_id=self.chat.id, + photo=photo, + caption=caption, + parse_mode=parse_mode, + ttl_seconds=ttl_seconds, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_poll( + self, + question: str, + options: List[str], + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_poll* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_poll( + chat_id=message.chat.id, + question="Is Pyrogram the best?", + options=["Yes", "Yes"] + ) + + Example: + .. code-block:: python + + message.reply_poll("Is Pyrogram the best?", ["Yes", "Yes"]) + + Args: + question (``str``): + The poll question, as string. + + options (List of ``str``): + The poll options, as list of strings (2 to 10 options are allowed). + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_poll( + chat_id=self.chat.id, + question=question, + options=options, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_sticker( + self, + sticker: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_sticker* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_sticker( + chat_id=message.chat.id, + sticker=sticker + ) + + Example: + .. code-block:: python + + message.reply_sticker(sticker) + + Args: + sticker (``str``): + Sticker to send. + Pass a file_id as string to send a sticker that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or + pass a file path as string to upload a new sticker that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_sticker( + chat_id=self.chat.id, + sticker=sticker, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_venue( + self, + latitude: float, + longitude: float, + title: str, + address: str, + quote: bool = None, + foursquare_id: str = "", + foursquare_type: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_venue* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_venue( + chat_id=message.chat.id, + latitude=41.890251, + longitude=12.492373, + title="Coliseum", + address="Piazza del Colosseo, 1, 00184 Roma RM" + ) + + Example: + .. code-block:: python + + message.reply_venue(41.890251, 12.492373, "Coliseum", "Piazza del Colosseo, 1, 00184 Roma RM") + + Args: + latitude (``float``): + Latitude of the venue. + + longitude (``float``): + Longitude of the venue. + + title (``str``): + Name of the venue. + + address (``str``): + Address of the venue. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + foursquare_id (``str``, *optional*): + Foursquare identifier of the venue. + + foursquare_type (``str``, *optional*): + Foursquare type of the venue, if known. + (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".) + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_venue( + chat_id=self.chat.id, + latitude=latitude, + longitude=longitude, + title=title, + address=address, + foursquare_id=foursquare_id, + foursquare_type=foursquare_type, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_video( + self, + video: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + supports_streaming: bool = True, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_video* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_video( + chat_id=message.chat.id, + video=video + ) + + Example: + .. code-block:: python + + message.reply_video(video) + + Args: + video (``str``): + Video to send. + Pass a file_id as string to send a video that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a video from the Internet, or + pass a file path as string to upload a new video that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Video caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + width (``int``, *optional*): + Video width. + + height (``int``, *optional*): + Video height. + + thumb (``str``, *optional*): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + supports_streaming (``bool``, *optional*): + Pass True, if the uploaded video is suitable for streaming. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_video( + chat_id=self.chat.id, + video=video, + caption=caption, + parse_mode=parse_mode, + duration=duration, + width=width, + height=height, + thumb=thumb, + supports_streaming=supports_streaming, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_video_note( + self, + video_note: str, + quote: bool = None, + duration: int = 0, + length: int = 1, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_video_note* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_video_note( + chat_id=message.chat.id, + video_note=video_note + ) + + Example: + .. code-block:: python + + message.reply_video_note(video_note) + + Args: + video_note (``str``): + Video note to send. + Pass a file_id as string to send a video note that exists on the Telegram servers, or + pass a file path as string to upload a new video note that exists on your local machine. + Sending video notes by a URL is currently unsupported. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + length (``int``, *optional*): + Video width and height. + + thumb (``str``, *optional*): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_video_note( + chat_id=self.chat.id, + video_note=video_note, + duration=duration, + length=length, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_voice( + self, + voice: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_voice* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_voice( + chat_id=message.chat.id, + voice=voice + ) + + Example: + .. code-block:: python + + message.reply_voice(voice) + + Args: + voice (``str``): + Audio file to send. + Pass a file_id as string to send an audio that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio from the Internet, or + pass a file path as string to upload a new audio that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Voice message caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the voice message in seconds. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_voice( + chat_id=self.chat.id, + voice=voice, + caption=caption, + parse_mode=parse_mode, + duration=duration, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def edit( + self, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": """Bound method *edit* of :obj:`Message ` Use as a shortcut for: @@ -696,7 +2354,7 @@ class Message(PyrogramType, Update): client.edit_message_text( chat_id=message.chat.id, message_id=message.message_id, - text="hello", + text="hello" ) Example: @@ -734,9 +2392,134 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def forward(self, - chat_id: int or str, - disable_notification: bool = None): + def edit_caption( + self, + caption: str, + parse_mode: str = "", + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *edit_caption* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_caption( + chat_id=message.chat.id, + message_id=message.message_id, + caption="hello" + ) + + Example: + .. code-block:: python + + message.edit_caption("hello") + + Args: + caption (``str``): + New caption of the message. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message. + Defaults to Markdown. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_caption( + chat_id=self.chat.id, + message_id=self.message_id, + caption=caption, + parse_mode=parse_mode, + reply_markup=reply_markup + ) + + def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + """Bound method *edit_media* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_media( + chat_id=message.chat.id, + message_id=message.message_id, + media=media + ) + + Example: + .. code-block:: python + + message.edit_media(media) + + Args: + media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`) + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_media( + chat_id=self.chat.id, + message_id=self.message_id, + media=media, + reply_markup=reply_markup + ) + + def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + """Bound method *edit_reply_markup* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_reply_markup( + chat_id=message.chat.id, + message_id=message.message_id, + reply_markup=inline_reply_markup + ) + + Example: + .. code-block:: python + + message.edit_reply_markup(inline_reply_markup) + + Args: + reply_markup (:obj:`InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + On success, if edited message is sent by the bot, the edited + :obj:`Message ` is returned, otherwise True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_reply_markup( + chat_id=self.chat.id, + message_id=self.message_id, + reply_markup=reply_markup + ) + + def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message": """Bound method *forward* of :obj:`Message `. Use as a shortcut for: @@ -746,7 +2529,7 @@ class Message(PyrogramType, Update): client.forward_messages( chat_id=chat_id, from_chat_id=message.chat.id, - message_ids=message.message_id, + message_ids=message.message_id ) Example: @@ -875,7 +2658,7 @@ class Message(PyrogramType, Update): ``TimeoutError``: If, after clicking an inline button, the bot fails to answer within 10 seconds """ if isinstance(self.reply_markup, pyrogram.ReplyKeyboardMarkup): - return self.reply(x) + return self.reply(x, quote=quote) elif isinstance(self.reply_markup, pyrogram.InlineKeyboardMarkup): if isinstance(x, int) and y is None: try: @@ -927,7 +2710,13 @@ class Message(PyrogramType, Update): else: raise ValueError("The message doesn't contain any keyboard") - def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = ()): + def download( + self, + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": """Bound method *download* of :obj:`Message `. Use as a shortcut for: @@ -976,6 +2765,40 @@ class Message(PyrogramType, Update): progress_args=progress_args, ) + def pin(self, disable_notification: bool = None) -> "Message": + """Bound method *pin* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.pin_chat_message( + chat_id=message.chat.id, + message_id=message_id + ) + + Example: + .. code-block:: python + + message.pin() + + Args: + disable_notification (``bool``): + Pass True, if it is not necessary to send a notification to all chat members about the new pinned + message. Notifications are always disabled in channels. + + Returns: + True on success. + + Raises: + :class:`Error ` + """ + return self._client.pin_chat_message( + chat_id=self.chat.id, + message_id=self.message_id, + disable_notification=disable_notification + ) + class Str(str): def __init__(self, *args): From 081b9b280a2a42d451b9a86b3daaa415bed6bfcd Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 21 Mar 2019 15:53:07 +0300 Subject: [PATCH 4/4] Add ability to forward messages as copies (#227) * Add ability to forward messages as copies * Add Messages.forward() method * Update and clean up code --- .../methods/messages/forward_messages.py | 85 +++++--- .../client/methods/messages/send_contact.py | 8 +- pyrogram/client/style/html.py | 2 +- pyrogram/client/style/markdown.py | 2 +- .../types/messages_and_media/message.py | 181 ++++++++++++++---- .../types/messages_and_media/messages.py | 54 +++++- pyrogram/client/types/pyrogram_type.py | 4 +- 7 files changed, 265 insertions(+), 71 deletions(-) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 35d406ae..39db4bee 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -29,7 +29,9 @@ class ForwardMessages(BaseClient): chat_id: Union[int, str], from_chat_id: Union[int, str], message_ids: Iterable[int], - disable_notification: bool = None + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False ) -> "pyrogram.Messages": """Use this method to forward messages of any kind. @@ -52,6 +54,15 @@ class ForwardMessages(BaseClient): Sends the message silently. Users will receive a notification with no sound. + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + Returns: On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded :obj:`Messages ` even if a list contains just one element, otherwise if @@ -61,35 +72,55 @@ class ForwardMessages(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ + is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] - r = self.send( - functions.messages.ForwardMessages( - to_peer=self.resolve_peer(chat_id), - from_peer=self.resolve_peer(from_chat_id), - id=message_ids, - silent=disable_notification or None, - random_id=[self.rnd_id() for _ in message_ids] - ) - ) - - messages = [] - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - messages.append( - pyrogram.Message._parse( - self, i.message, - users, chats + if as_copy: + sent_messages = [] + for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]: + messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages + for message in messages.messages: + sent_messages.append( + message.forward( + chat_id, + disable_notification=disable_notification, + as_copy=True, + remove_caption=remove_caption + ) ) + return pyrogram.Messages( + client=self, + total_count=len(sent_messages), + messages=sent_messages + ) if is_iterable else sent_messages[0] + else: + r = self.send( + functions.messages.ForwardMessages( + to_peer=self.resolve_peer(chat_id), + from_peer=self.resolve_peer(from_chat_id), + id=message_ids, + silent=disable_notification or None, + random_id=[self.rnd_id() for _ in message_ids] ) + ) - return pyrogram.Messages( - client=self, - total_count=len(messages), - messages=messages - ) if is_iterable else messages[0] + forwarded_messages = [] + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + forwarded_messages.append( + pyrogram.Message._parse( + self, i.message, + users, chats + ) + ) + + return pyrogram.Messages( + client=self, + total_count=len(forwarded_messages), + messages=forwarded_messages + ) if is_iterable else forwarded_messages[0] diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index bed9a37e..14ce61ec 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -29,8 +29,8 @@ class SendContact(BaseClient): chat_id: Union[int, str], phone_number: str, first_name: str, - last_name: str = "", - vcard: str = "", + last_name: str = None, + vcard: str = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -83,8 +83,8 @@ class SendContact(BaseClient): media=types.InputMediaContact( phone_number=phone_number, first_name=first_name, - last_name=last_name, - vcard=vcard + last_name=last_name or "", + vcard=vcard or "" ), message="", silent=disable_notification or None, diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 040e770b..211716e1 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -40,7 +40,7 @@ class HTML: def parse(self, message: str): entities = [] - message = utils.add_surrogates(str(message)) + message = utils.add_surrogates(str(message or "")) offset = 0 for match in self.HTML_RE.finditer(message): diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 04ce95c8..45037a35 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -56,7 +56,7 @@ class Markdown: self.peers_by_id = peers_by_id def parse(self, message: str): - message = utils.add_surrogates(str(message)).strip() + message = utils.add_surrogates(str(message or "")).strip() entities = [] offset = 0 diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 558efd38..4ead0d30 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -16,12 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from functools import partial from typing import List, Match, Union import pyrogram from pyrogram.api import types from pyrogram.api.errors import MessageIdsEmpty -from pyrogram.client.ext import ChatAction +from pyrogram.client.ext import ChatAction, ParseMode from pyrogram.client.types.input_media import InputMedia from .contact import Contact from .location import Location @@ -33,6 +34,32 @@ from ..user_and_chats.chat import Chat from ..user_and_chats.user import User +class Str(str): + def __init__(self, *args): + super().__init__() + + self._client = None + self._entities = None + + def init(self, client, entities): + self._client = client + self._entities = entities + + return self + + @property + def text(self): + return self + + @property + def markdown(self): + return self._client.markdown.unparse(self, self._entities) + + @property + def html(self): + return self._client.html.unparse(self, self._entities) + + class Message(PyrogramType, Update): """This object represents a message. @@ -268,7 +295,7 @@ class Message(PyrogramType, Update): edit_date: int = None, media_group_id: str = None, author_signature: str = None, - text: str = None, + text: Str = None, entities: List["pyrogram.MessageEntity"] = None, caption_entities: List["pyrogram.MessageEntity"] = None, audio: "pyrogram.Audio" = None, @@ -280,7 +307,7 @@ class Message(PyrogramType, Update): video: "pyrogram.Video" = None, voice: "pyrogram.Voice" = None, video_note: "pyrogram.VideoNote" = None, - caption: str = None, + caption: Str = None, contact: "pyrogram.Contact" = None, location: "pyrogram.Location" = None, venue: "pyrogram.Venue" = None, @@ -2519,7 +2546,13 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message": + def forward( + self, + chat_id: int or str, + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False + ) -> "Message": """Bound method *forward* of :obj:`Message `. Use as a shortcut for: @@ -2547,18 +2580,120 @@ class Message(PyrogramType, Update): Sends the message silently. Users will receive a notification with no sound. + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + Returns: On success, the forwarded Message is returned. Raises: :class:`Error ` """ - return self._client.forward_messages( - chat_id=chat_id, - from_chat_id=self.chat.id, - message_ids=self.message_id, - disable_notification=disable_notification - ) + if as_copy: + if self.service: + raise ValueError("Unable to copy service messages") + + if self.game and not self._client.is_bot: + raise ValueError("Users cannot send messages with Game media type") + + # TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users + # can"t choose. + + if self.text: + return self._client.send_message( + chat_id, + text=self.text.html, + parse_mode="html", + disable_web_page_preview=not self.web_page, + disable_notification=disable_notification + ) + elif self.media: + caption = self.caption.html if self.caption and not remove_caption else None + + send_media = partial( + self._client.send_cached_media, + chat_id=chat_id, + disable_notification=disable_notification + ) + + if self.photo: + file_id = self.photo.sizes[-1].file_id + elif self.audio: + file_id = self.audio.file_id + elif self.document: + file_id = self.document.file_id + elif self.video: + file_id = self.video.file_id + elif self.animation: + file_id = self.animation.file_id + elif self.voice: + file_id = self.voice.file_id + elif self.sticker: + file_id = self.sticker.file_id + elif self.video_note: + file_id = self.video_note.file_id + elif self.contact: + return self._client.send_contact( + chat_id, + phone_number=self.contact.phone_number, + first_name=self.contact.first_name, + last_name=self.contact.last_name, + vcard=self.contact.vcard, + disable_notification=disable_notification + ) + elif self.location: + return self._client.send_location( + chat_id, + latitude=self.location.latitude, + longitude=self.location.longitude, + disable_notification=disable_notification + ) + elif self.venue: + return self._client.send_venue( + chat_id, + latitude=self.venue.location.latitude, + longitude=self.venue.location.longitude, + title=self.venue.title, + address=self.venue.address, + foursquare_id=self.venue.foursquare_id, + foursquare_type=self.venue.foursquare_type, + disable_notification=disable_notification + ) + elif self.poll: + return self._client.send_poll( + chat_id, + question=self.poll.question, + options=[opt.text for opt in self.poll.options], + disable_notification=disable_notification + ) + elif self.game: + return self._client.send_game( + chat_id, + game_short_name=self.game.short_name, + disable_notification=disable_notification + ) + else: + raise ValueError("Unknown media type") + + if self.sticker or self.video_note: # Sticker and VideoNote should have no caption + return send_media(file_id) + else: + return send_media(file_id=file_id, caption=caption, parse_mode=ParseMode.HTML) + else: + raise ValueError("Can't copy this message") + else: + return self._client.forward_messages( + chat_id=chat_id, + from_chat_id=self.chat.id, + message_ids=self.message_id, + disable_notification=disable_notification + ) def delete(self, revoke: bool = True): """Bound method *delete* of :obj:`Message `. @@ -2798,29 +2933,3 @@ class Message(PyrogramType, Update): message_id=self.message_id, disable_notification=disable_notification ) - - -class Str(str): - def __init__(self, *args): - super().__init__() - - self.client = None - self.entities = None - - def init(self, client, entities): - self.client = client - self.entities = entities - - return self - - @property - def text(self): - return self - - @property - def markdown(self): - return self.client.markdown.unparse(self, self.entities) - - @property - def html(self): - return self.client.html.unparse(self, self.entities) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 9983a01d..aae31a82 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union import pyrogram from pyrogram.api import types @@ -116,3 +116,55 @@ class Messages(PyrogramType, Update): messages=parsed_messages, client=client ) + + def forward( + self, + chat_id: Union[int, str], + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False + ): + """Bound method *forward* of :obj:`Message `. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + disable_notification (``bool``, *optional*): + Sends messages silently. + Users will receive a notification with no sound. + + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + + Returns: + On success, a :class:`Messages ` containing forwarded messages is returned. + + Raises: + :class:`Error ` + """ + forwarded_messages = [] + + for message in self.messages: + forwarded_messages.append( + message.forward( + chat_id=chat_id, + as_copy=as_copy, + disable_notification=disable_notification, + remove_caption=remove_caption + ) + ) + + return Messages( + total_count=len(forwarded_messages), + messages=forwarded_messages, + client=self._client + ) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index d746e6a7..af828926 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -19,11 +19,13 @@ from collections import OrderedDict from json import dumps +import pyrogram + class PyrogramType: __slots__ = ["_client"] - def __init__(self, client): + def __init__(self, client: "pyrogram.client.ext.BaseClient"): self._client = client def __str__(self):