From c6691dc6a8b7decbd9c1bd2c9dd1d14c7f36650b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 7 May 2019 21:25:55 +0200 Subject: [PATCH] Update the reference with even more types and other docs --- readthedocs/extra/reference.rst | 223 +++++++++++++++++++++++++++- telethon/events/messagedeleted.py | 15 ++ telethon/tl/custom/adminlogevent.py | 36 ++--- telethon/tl/custom/button.py | 24 +-- telethon/tl/custom/conversation.py | 12 +- telethon/tl/custom/message.py | 107 ++++++------- telethon/utils.py | 14 +- 7 files changed, 339 insertions(+), 92 deletions(-) diff --git a/readthedocs/extra/reference.rst b/readthedocs/extra/reference.rst index 478884d9..3d73861c 100644 --- a/readthedocs/extra/reference.rst +++ b/readthedocs/extra/reference.rst @@ -17,7 +17,8 @@ property that you are interested in. TelegramClient ============== -This is a summary of the methods you will find at :ref:`telethon-client`. +This is a summary of the methods and +properties you will find at :ref:`telethon-client`. Auth ---- @@ -185,3 +186,223 @@ Account takeout end_takeout + + +Message +======= + +.. currentmodule:: telethon.tl.custom.message + +The `Message` type is very important, mostly because we are working +with a library for a *messaging* platform, so messages are widely used: +in events, when fetching history, replies, etc. + +Properties +---------- + +.. note:: + + We document *custom properties* here, not all the attributes of the + `Message` (which is the information Telegram actually returns). + +.. currentmodule:: telethon.tl.custom.message.Message + +.. autosummary:: + :nosignatures: + + text + raw_text + is_reply + forward + buttons + button_count + photo + document + web_preview + audio + voice + video + video_note + gif + sticker + contact + game + geo + invoice + poll + venue + action_entities + via_bot + via_input_bot + client + + +Methods +------- + +.. autosummary:: + :nosignatures: + + respond + reply + forward_to + edit + delete + get_reply_message + click + download_media + get_entities_text + get_buttons + + +Conversation +============ + +The `Conversation ` object +is returned by the `client.conversation() +` method to easily +send and receive responses like a normal conversation. + +.. currentmodule:: telethon.tl.custom.conversation.Conversation + +.. autosummary:: + :nosignatures: + + send_message + send_file + mark_read + get_response + get_reply + get_edit + wait_read + wait_event + cancel + + +AdminLogEvent +============= + +The `AdminLogEvent ` object +is returned by the `client.iter_admin_log() +` method to easily iterate +over past "events" (deleted messages, edits, title changes, leaving members…) + +These are all the properties you can find in it: + +.. currentmodule:: telethon.tl.custom.adminlogevent.AdminLogEvent + +.. autosummary:: + :nosignatures: + + id + date + user_id + action + old + new + changed_about + changed_title + changed_username + changed_photo + changed_sticker_set + changed_message + deleted_message + changed_admin + changed_restrictions + changed_invites + joined + joined_invite + left + changed_hide_history + changed_signatures + changed_pin + changed_default_banned_rights + stopped_poll + + +Button +====== + +The `Button ` class is used when you login +as a bot account to send messages with reply markup, such as inline buttons +or custom keyboards. + +These are the static methods you can use to create instances of the markup: + +.. currentmodule:: telethon.tl.custom.button.Button + +.. autosummary:: + :nosignatures: + + inline + switch_inline + url + text + request_location + request_phone + clear + force_reply + + +InlineResult +============ + +The `InlineResult ` object +is returned inside a list by the `client.inline_query() +` method to make an inline +query to a bot that supports being used in inline mode, such as ``@like``. + +Note that the list returned is in fact a *subclass* of a list called +`InlineResults `, which, +in addition of being a list (iterator, indexed access, etc.), has extra +attributes and methods. + +These are the constants for the types, properties and methods you +can find the individual results: + +.. currentmodule:: telethon.tl.custom.inlineresult.InlineResult + +.. autosummary:: + :nosignatures: + + ARTICLE + PHOTO + GIF + VIDEO + VIDEO_GIF + AUDIO + DOCUMENT + LOCATION + VENUE + CONTACT + GAME + type + message + title + description + url + photo + document + click + download_media + + +Utils +===== + +The `telethon.utils` module has plenty of methods that make using the +library a lot easier. Only the interesting ones will be listed here. + +.. currentmodule:: telethon.utils + +.. autosummary:: + :nosignatures: + + get_display_name + get_extension + get_inner_text + get_peer_id + resolve_id + pack_bot_file_id + resolve_bot_file_id + resolve_invite_link diff --git a/telethon/events/messagedeleted.py b/telethon/events/messagedeleted.py index f13ed6ee..7312c163 100644 --- a/telethon/events/messagedeleted.py +++ b/telethon/events/messagedeleted.py @@ -6,6 +6,21 @@ from ..tl import types class MessageDeleted(EventBuilder): """ Event fired when one or more messages are deleted. + + .. important:: + + Telegram **does not** send information about *where* a message + was deleted if it occurs in private conversations with other users + or in small group chats, because message IDs are *unique* and you + can identify the chat with the message ID alone if you saved it + previously. + + Telethon **does not** save information of where messages occur, + so it cannot know in which chat a message was deleted (this will + only work in channels, where the channel ID *is* present). + + This means that the ``chats=`` parameter will not work reliably, + unless you intend on working with channels and super-groups only. """ @classmethod def build(cls, update): diff --git a/telethon/tl/custom/adminlogevent.py b/telethon/tl/custom/adminlogevent.py index b66101e6..37f903bc 100644 --- a/telethon/tl/custom/adminlogevent.py +++ b/telethon/tl/custom/adminlogevent.py @@ -127,7 +127,7 @@ class AdminLogEvent: @property def changed_about(self): """ - Whether the channel's about was changed in this event or not. + Whether the channel's about was changed or not. If ``True``, `old` and `new` will be present as ``str``. """ @@ -137,7 +137,7 @@ class AdminLogEvent: @property def changed_title(self): """ - Whether the channel's title was changed in this event or not. + Whether the channel's title was changed or not. If ``True``, `old` and `new` will be present as ``str``. """ @@ -147,7 +147,7 @@ class AdminLogEvent: @property def changed_username(self): """ - Whether the channel's username was changed in this event or not. + Whether the channel's username was changed or not. If ``True``, `old` and `new` will be present as ``str``. """ @@ -157,7 +157,7 @@ class AdminLogEvent: @property def changed_photo(self): """ - Whether the channel's photo was changed in this event or not. + Whether the channel's photo was changed or not. If ``True``, `old` and `new` will be present as :tl:`Photo`. """ @@ -167,7 +167,7 @@ class AdminLogEvent: @property def changed_sticker_set(self): """ - Whether the channel's sticker set was changed in this event or not. + Whether the channel's sticker set was changed or not. If ``True``, `old` and `new` will be present as :tl:`InputStickerSet`. """ @@ -177,7 +177,7 @@ class AdminLogEvent: @property def changed_message(self): """ - Whether a message in this channel was edited in this event or not. + Whether a message in this channel was edited or not. If ``True``, `old` and `new` will be present as `Message `. @@ -188,7 +188,7 @@ class AdminLogEvent: @property def deleted_message(self): """ - Whether a message in this channel was deleted in this event or not. + Whether a message in this channel was deleted or not. If ``True``, `old` will be present as `Message `. @@ -200,7 +200,7 @@ class AdminLogEvent: def changed_admin(self): """ Whether the permissions for an admin in this channel - changed in this event or not. + changed or not. If ``True``, `old` and `new` will be present as :tl:`ChannelParticipant`. @@ -212,7 +212,7 @@ class AdminLogEvent: @property def changed_restrictions(self): """ - Whether a message in this channel was edited in this event or not. + Whether a message in this channel was edited or not. If ``True``, `old` and `new` will be present as :tl:`ChannelParticipant`. @@ -224,7 +224,7 @@ class AdminLogEvent: @property def changed_invites(self): """ - Whether the invites in the channel were toggled in this event or not. + Whether the invites in the channel were toggled or not. If ``True``, `old` and `new` will be present as ``bool``. """ @@ -235,7 +235,7 @@ class AdminLogEvent: def joined(self): """ Whether `user` joined through the channel's - public username in this event or not. + public username or not. """ return isinstance(self.original.action, types.ChannelAdminLogEventActionParticipantJoin) @@ -244,7 +244,7 @@ class AdminLogEvent: def joined_invite(self): """ Whether a new user joined through an invite - link to the channel in this event or not. + link to the channel or not. If ``True``, `new` will be present as :tl:`ChannelParticipant`. @@ -255,7 +255,7 @@ class AdminLogEvent: @property def left(self): """ - Whether `user` left the channel in this event or not. + Whether `user` left the channel or not. """ return isinstance(self.original.action, types.ChannelAdminLogEventActionParticipantLeave) @@ -264,7 +264,7 @@ class AdminLogEvent: def changed_hide_history(self): """ Whether hiding the previous message history for new members - in the channel were toggled in this event or not. + in the channel was toggled or not. If ``True``, `old` and `new` will be present as ``bool``. """ @@ -275,7 +275,7 @@ class AdminLogEvent: def changed_signatures(self): """ Whether the message signatures in the channel were toggled - in this event or not. + or not. If ``True``, `old` and `new` will be present as ``bool``. """ @@ -285,7 +285,7 @@ class AdminLogEvent: @property def changed_pin(self): """ - Whether a new message in this channel was pinned in this event or not. + Whether a new message in this channel was pinned or not. If ``True``, `new` will be present as `Message `. @@ -296,7 +296,7 @@ class AdminLogEvent: @property def changed_default_banned_rights(self): """ - Whether the default banned rights were changed in this event or not. + Whether the default banned rights were changed or not. If ``True``, `old` and `new` will be present as :tl:`ChatBannedRights`. @@ -307,7 +307,7 @@ class AdminLogEvent: @property def stopped_poll(self): """ - Whether a poll was stopped in this event or not. + Whether a poll was stopped or not. If ``True``, `new` will be present as `Message `. diff --git a/telethon/tl/custom/button.py b/telethon/tl/custom/button.py index 55fb9641..2bfb4018 100644 --- a/telethon/tl/custom/button.py +++ b/telethon/tl/custom/button.py @@ -54,7 +54,7 @@ class Button: @staticmethod def inline(text, data=None): """ - Creates a new inline button. + Creates a new inline button with some payload data in it. If `data` is omitted, the given `text` will be used as `data`. In any case `data` should be either ``bytes`` or ``str``. @@ -75,7 +75,7 @@ class Button: @staticmethod def switch_inline(text, query='', same_peer=False): """ - Creates a new button to switch to inline query. + Creates a new inline button to switch to inline query. If `query` is given, it will be the default text to be used when making the inline query. @@ -89,16 +89,18 @@ class Button: @staticmethod def url(text, url=None): """ - Creates a new button to open the desired URL upon clicking it. + Creates a new inline button to open the desired URL on click. If no `url` is given, the `text` will be used as said URL instead. + + You cannot detect that the user clicked this button directly. """ return types.KeyboardButtonUrl(text, url or text) @classmethod def text(cls, text, *, resize=None, single_use=None, selective=None): """ - Creates a new button with the given text. + Creates a new keyboard button with the given text. Args: resize (`bool`): @@ -122,8 +124,7 @@ class Button: def request_location(cls, text, *, resize=None, single_use=None, selective=None): """ - Creates a new button that will request - the user's location upon being clicked. + Creates a new keyboard button to request the user's location on click. ``resize``, ``single_use`` and ``selective`` are documented in `text`. """ @@ -134,8 +135,7 @@ class Button: def request_phone(cls, text, *, resize=None, single_use=None, selective=None): """ - Creates a new button that will request - the user's phone number upon being clicked. + Creates a new keyboard button to request the user's phone on click. ``resize``, ``single_use`` and ``selective`` are documented in `text`. """ @@ -145,15 +145,15 @@ class Button: @staticmethod def clear(): """ - Clears all the buttons. When used, no other - button should be present or it will be ignored. + Clears all keyboard buttons after sending a message with this markup. + When used, no other button should be present or it will be ignored. """ return types.ReplyKeyboardHide() @staticmethod def force_reply(): """ - Forces a reply. If used, no other button - should be present or it will be ignored. + Forces a reply to the message with this markup. If used, + no other button should be present or it will be ignored. """ return types.ReplyKeyboardForceReply() diff --git a/telethon/tl/custom/conversation.py b/telethon/tl/custom/conversation.py index 60e994fc..7a37efec 100644 --- a/telethon/tl/custom/conversation.py +++ b/telethon/tl/custom/conversation.py @@ -115,7 +115,7 @@ class Conversation(ChatGetter): async def get_response(self, message=None, *, timeout=None): """ - Returns a coroutine that will resolve once a response arrives. + Gets the next message that responds to a previous one. Args: message (`Message ` | `int`, optional): @@ -133,9 +133,7 @@ class Conversation(ChatGetter): async def get_reply(self, message=None, *, timeout=None): """ - Returns a coroutine that will resolve once a reply - (that is, a message being a reply) arrives. The - arguments are the same as those for `get_response`. + Gets the next message that explicitly replies to a previous one. """ return await self._get_message( message, self._reply_indices, self._pending_replies, timeout, @@ -229,9 +227,9 @@ class Conversation(ChatGetter): async def wait_read(self, message=None, *, timeout=None): """ - Awaits for the sent message to be read. Note that receiving - a response doesn't imply the message was read, and this action - will also trigger even without a response. + Awaits for the sent message to be marked as read. Note that + receiving a response doesn't imply the message was read, and + this action will also trigger even without a response. """ start_time = time.time() future = self._client.loop.create_future() diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 509a1462..f105f615 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -252,8 +252,8 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def client(self): """ - Returns the `telethon.client.telegramclient.TelegramClient` - that patched this message. This will only be present if you + Returns the `TelegramClient ` + that *patched* this message. This will only be present if you **use the friendly methods**, it won't be there if you invoke raw API methods manually, in which case you should only access members, not properties. @@ -300,7 +300,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def is_reply(self): """ - True if the message is a reply to some other. + ``True`` if the message is a reply to some other message. Remember that you can access the ID of the message this one is replying to through `reply_to_msg_id`, @@ -311,17 +311,19 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def forward(self): """ - Returns `Forward ` - if the message has been forwarded from somewhere else. + The `Forward ` + information if this message is a forwarded message. """ return self._forward @property def buttons(self): """ - Returns a matrix (list of lists) containing all buttons of the message - as `MessageButton ` - instances. + Returns a list of lists of `MessageButton + `, + if any. + + Otherwise, it returns ``None``. """ if self._buttons is None and self.reply_markup: if not self.input_chat: @@ -337,8 +339,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): async def get_buttons(self): """ - Returns `buttons`, but will make an API call to find the - input chat (needed for the buttons) unless it's already cached. + Returns `buttons` when that property fails (this is rarely needed). """ if not self.buttons and self.reply_markup: chat = await self.get_input_chat() @@ -357,7 +358,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def button_count(self): """ - Returns the total button count. + Returns the total button count (sum of all `buttons` rows). """ if self._buttons_count is None: if isinstance(self.reply_markup, ( @@ -372,9 +373,11 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def photo(self): """ - If the message media is a photo, this returns the :tl:`Photo` object. - This will also return the photo for :tl:`MessageService` if their - action is :tl:`MessageActionChatEditPhoto`. + The :tl:`Photo` media in this message, if any. + + This will also return the photo for :tl:`MessageService` if its + action is :tl:`MessageActionChatEditPhoto`, or if the message has + a web preview with a photo. """ if isinstance(self.media, types.MessageMediaPhoto): if isinstance(self.media.photo, types.Photo): @@ -389,8 +392,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def document(self): """ - If the message media is a document, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if any. """ if isinstance(self.media, types.MessageMediaDocument): if isinstance(self.media.document, types.Document): @@ -403,8 +405,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def web_preview(self): """ - If the message has a loaded web preview, - this returns the :tl:`WebPage` object. + The :tl:`WebPage` media in this message, if any. """ if isinstance(self.media, types.MessageMediaWebPage): if isinstance(self.media.webpage, types.WebPage): @@ -413,8 +414,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def audio(self): """ - If the message media is a document with an Audio attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's an audio file. """ return self._document_by_attribute(types.DocumentAttributeAudio, lambda attr: not attr.voice) @@ -422,8 +422,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def voice(self): """ - If the message media is a document with a Voice attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's a voice note. """ return self._document_by_attribute(types.DocumentAttributeAudio, lambda attr: attr.voice) @@ -431,16 +430,14 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def video(self): """ - If the message media is a document with a Video attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's a video. """ return self._document_by_attribute(types.DocumentAttributeVideo) @property def video_note(self): """ - If the message media is a document with a Video attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's a video note. """ return self._document_by_attribute(types.DocumentAttributeVideo, lambda attr: attr.round_message) @@ -448,24 +445,25 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def gif(self): """ - If the message media is a document with an Animated attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's a "gif". + + "Gif" files by Telegram are normally ``.mp4`` video files without + sound, the so called "animated" media. However, it may be the actual + gif format if the file is too large. """ return self._document_by_attribute(types.DocumentAttributeAnimated) @property def sticker(self): """ - If the message media is a document with a Sticker attribute, - this returns the :tl:`Document` object. + The :tl:`Document` media in this message, if it's a sticker. """ return self._document_by_attribute(types.DocumentAttributeSticker) @property def contact(self): """ - If the message media is a contact, - this returns the :tl:`MessageMediaContact`. + The :tl:`MessageMediaContact` in this message, if it's a contact. """ if isinstance(self.media, types.MessageMediaContact): return self.media @@ -473,7 +471,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def game(self): """ - If the message media is a game, this returns the :tl:`Game`. + The :tl:`Game` media in this message, if it's a game. """ if isinstance(self.media, types.MessageMediaGame): return self.media.game @@ -481,8 +479,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def geo(self): """ - If the message media is geo, geo live or a venue, - this returns the :tl:`GeoPoint`. + The :tl:`GeoPoint` media in this message, if it has a location. """ if isinstance(self.media, (types.MessageMediaGeo, types.MessageMediaGeoLive, @@ -492,8 +489,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def invoice(self): """ - If the message media is an invoice, - this returns the :tl:`MessageMediaInvoice`. + The :tl:`MessageMediaInvoice` in this message, if it's an invoice. """ if isinstance(self.media, types.MessageMediaInvoice): return self.media @@ -501,8 +497,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def poll(self): """ - If the message media is a poll, - this returns the :tl:`MessageMediaPoll`. + The :tl:`MessageMediaPoll` in this message, if it's a poll. """ if isinstance(self.media, types.MessageMediaPoll): return self.media @@ -510,8 +505,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def venue(self): """ - If the message media is a venue, - this returns the :tl:`MessageMediaVenue`. + The :tl:`MessageMediaVenue` in this message, if it's a venue. """ if isinstance(self.media, types.MessageMediaVenue): return self.media @@ -535,11 +529,10 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @property def via_bot(self): """ - If this message was sent via some bot (i.e. `via_bot_id` is not - ``None``), this property returns the :tl:`User` of the bot that - was used to send this message. + The bot :tl:`User` if the message was sent via said bot. - Returns ``None`` otherwise (or if the bot entity is unknown). + This will only be present if `via_bot_id` is not ``None`` and + the entity is known. """ return self._via_bot @@ -556,8 +549,22 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): def get_entities_text(self, cls=None): """ - Returns a list of tuples [(:tl:`MessageEntity`, `str`)], the string - being the inner text of the message entity (like bold, italics, etc). + Returns a list of ``(markup entity, inner text)`` + (like bold or italics). + + The markup entity is a :tl:`MessageEntity` that represents bold, + italics, etc., and the inner text is the ``str`` inside that markup + entity. + + For example: + + .. code-block:: python + + print(repr(message.text)) # shows: 'Hello **world**!' + + for ent, txt in message.get_entities_text(): + print(ent) # shows: MessageEntityBold(offset=6, length=5) + print(txt) # shows: world Args: cls (`type`): @@ -709,8 +716,8 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): async def click(self, i=None, j=None, *, text=None, filter=None, data=None): """ - Calls `telethon.tl.custom.messagebutton.MessageButton.click` - for the specified button. + Calls `button.click ` + on the specified button. Does nothing if the message has no buttons. @@ -855,7 +862,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): to know what bot we want to start. Raises ``ValueError`` if the bot cannot be found but is needed. Returns ``None`` if it's not needed. """ - if self._client and not isinstance(self.reply_markup, ( + if self._client and not isinstance(self.reply_markup, ( types.ReplyInlineMarkup, types.ReplyKeyboardMarkup)): return None diff --git a/telethon/utils.py b/telethon/utils.py index 64317bd5..1713c9e3 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -83,7 +83,7 @@ def chunks(iterable, size=100): def get_display_name(entity): """ - Gets the display name for the given entity, if it's an :tl:`User`, + Gets the display name for the given :tl:`User`, :tl:`Chat` or :tl:`Channel`. Returns an empty string otherwise. """ if isinstance(entity, types.User): @@ -810,9 +810,15 @@ def get_peer(peer): def get_peer_id(peer, add_mark=True): """ - Finds the ID of the given peer, and converts it to the "bot api" format - so it the peer can be identified back. User ID is left unmodified, - chat ID is negated, and channel ID is prefixed with -100. + Convert the given peer into its marked ID by default. + + This "mark" comes from the "bot api" format, and with it the peer type + can be identified back. User ID is left unmodified, chat ID is negated, + and channel ID is prefixed with -100: + + * ``user_id`` + * ``-chat_id`` + * ``-100channel_id`` The original ID and the peer type class can be returned with a call to :meth:`resolve_id(marked_id)`.