From c85ba4accc78b24e3400b7612423e07a2237c28b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 20 Jun 2018 11:05:33 +0200 Subject: [PATCH] Revisit documentation, cross-references and unnecessary indents --- .../advanced-usage/accessing-the-full-api.rst | 92 +++---- readthedocs/extra/advanced-usage/sessions.rst | 6 +- readthedocs/extra/basic/creating-a-client.rst | 195 ++++++++------- readthedocs/extra/basic/entities.rst | 36 ++- readthedocs/extra/basic/getting-started.rst | 88 +++---- readthedocs/extra/basic/installation.rst | 45 +++- readthedocs/extra/basic/telegram-client.rst | 68 +++--- .../extra/basic/working-with-updates.rst | 90 ++++--- .../extra/developing/project-structure.rst | 37 +-- readthedocs/extra/developing/test-servers.rst | 14 +- .../tips-for-porting-the-project.rst | 2 +- readthedocs/extra/examples/bots.rst | 38 +-- .../extra/examples/chats-and-channels.rst | 230 +++++++++--------- readthedocs/extra/examples/users.rst | 34 +-- .../extra/examples/working-with-messages.rst | 120 ++++----- .../extra/troubleshooting/enable-logging.rst | 22 +- readthedocs/extra/wall-of-shame.rst | 12 +- telethon/client/updates.py | 11 +- telethon/events/common.py | 27 +- telethon/tl/custom/message.py | 6 + 20 files changed, 651 insertions(+), 522 deletions(-) diff --git a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst b/readthedocs/extra/advanced-usage/accessing-the-full-api.rst index c27d9a02..016395ac 100644 --- a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst +++ b/readthedocs/extra/advanced-usage/accessing-the-full-api.rst @@ -11,11 +11,11 @@ Accessing the Full API reason not to, like a method not existing or you wanting more control. -The `telethon.telegram_client.TelegramClient` doesn't offer a method for every -single request the Telegram API supports. However, it's very simple to *call* -or *invoke* any request. Whenever you need something, don't forget to `check -the documentation`__ and look for the `method you need`__. There you can go -through a sorted list of everything you can do. +The :ref:`TelegramClient ` doesn't offer a method for +every single request the Telegram API supports. However, it's very simple to +*call* or *invoke* any request. Whenever you need something, don't forget to +`check the documentation`__ and look for the `method you need`__. There you +can go through a sorted list of everything you can do. .. note:: @@ -30,7 +30,8 @@ You should also refer to the documentation to see what the objects (constructors) Telegram returns look like. Every constructor inherits from a common type, and that's the reason for this distinction. -Say `telethon.telegram_client.TelegramClient.send_message` didn't exist, +Say `client.send_message +` didn't exist, we could use the `search`__ to look for "message". There we would find :tl:`SendMessageRequest`, which we can work with. @@ -39,16 +40,16 @@ to invoke it. You can also call ``help(request)`` for information on what input parameters it takes. Remember to "Copy import to the clipboard", or your script won't be aware of this class! Now we have: - .. code-block:: python - - from telethon.tl.functions.messages import SendMessageRequest +.. code-block:: python + + from telethon.tl.functions.messages import SendMessageRequest If you're going to use a lot of these, you may do: - .. code-block:: python - - from telethon.tl import types, functions - # We now have access to 'functions.messages.SendMessageRequest' +.. code-block:: python + + from telethon.tl import types, functions + # We now have access to 'functions.messages.SendMessageRequest' We see that this request must take at least two parameters, a ``peer`` of type :tl:`InputPeer`, and a ``message`` which is just a Python @@ -57,74 +58,79 @@ of type :tl:`InputPeer`, and a ``message`` which is just a Python How can we retrieve this :tl:`InputPeer`? We have two options. We manually construct one, for instance: - .. code-block:: python +.. code-block:: python - from telethon.tl.types import InputPeerUser + from telethon.tl.types import InputPeerUser - peer = InputPeerUser(user_id, user_hash) + peer = InputPeerUser(user_id, user_hash) -Or we call `telethon.telegram_client.TelegramClient.get_input_entity()`: +Or we call `client.get_input_entity +`: - .. code-block:: python +.. code-block:: python - peer = client.get_input_entity('someone') + peer = client.get_input_entity('someone') When you're going to invoke an API method, most require you to pass an :tl:`InputUser`, :tl:`InputChat`, or so on, this is why using -``.get_input_entity()`` is more straightforward (and often -immediate, if you've seen the user before, know their ID, etc.). -If you also need to have information about the whole user, use -`telethon.telegram_client.TelegramClient.get_entity()` instead: +`client.get_input_entity ` +is more straightforward (and often immediate, if you've seen the user before, +know their ID, etc.). If you also **need** to have information about the whole +user, use `client.get_entity ` +instead: - .. code-block:: python +.. code-block:: python - entity = client.get_entity('someone') + entity = client.get_entity('someone') In the later case, when you use the entity, the library will cast it to its "input" version for you. If you already have the complete user and want to cache its input version so the library doesn't have to do this every time its used, simply call `telethon.utils.get_input_peer`: - .. code-block:: python +.. code-block:: python - from telethon import utils - peer = utils.get_input_user(entity) + from telethon import utils + peer = utils.get_input_user(entity) .. note:: Since ``v0.16.2`` this is further simplified. The ``Request`` itself - will call ``client.get_input_entity()`` for you when required, but - it's good to remember what's happening. + will call `client.get_input_entity < + telethon.client.users.UserMethods.get_input_entity>` for you when required, + but it's good to remember what's happening. -After this small parenthesis about ``.get_entity`` versus -``.get_input_entity``, we have everything we need. To ``.invoke()`` our +After this small parenthesis about `client.get_entity +` versus +`client.get_input_entity `, +we have everything we need. To invoke our request we do: - .. code-block:: python +.. code-block:: python - result = client(SendMessageRequest(peer, 'Hello there!')) - # __call__ is an alias for client.invoke(request). Both will work + result = client(SendMessageRequest(peer, 'Hello there!')) + # __call__ is an alias for client.invoke(request). Both will work Message sent! Of course, this is only an example. There are nearly 250 methods available as of layer 73, and you can use every single of them as you wish. Remember to use the right types! To sum up: - .. code-block:: python +.. code-block:: python - result = client(SendMessageRequest( - client.get_input_entity('username'), 'Hello there!' - )) + result = client(SendMessageRequest( + client.get_input_entity('username'), 'Hello there!' + )) This can further be simplified to: - .. code-block:: python +.. code-block:: python - result = client(SendMessageRequest('username', 'Hello there!')) - # Or even - result = client(SendMessageRequest(PeerChannel(id), 'Hello there!')) + result = client(SendMessageRequest('username', 'Hello there!')) + # Or even + result = client(SendMessageRequest(PeerChannel(id), 'Hello there!')) .. note:: diff --git a/readthedocs/extra/advanced-usage/sessions.rst b/readthedocs/extra/advanced-usage/sessions.rst index 1cbd0b3a..eda74e7a 100644 --- a/readthedocs/extra/advanced-usage/sessions.rst +++ b/readthedocs/extra/advanced-usage/sessions.rst @@ -4,7 +4,8 @@ Session Files ============== -The first parameter you pass to the constructor of the ``TelegramClient`` is +The first parameter you pass to the constructor of the +:ref:`TelegramClient ` is the ``session``, and defaults to be the session name (or full path). That is, if you create a ``TelegramClient('anon')`` instance and connect, an ``anon.session`` file will be created in the working directory. @@ -42,7 +43,8 @@ If you don't want to use the default SQLite session storage, you can also use one of the other implementations or implement your own storage. To use a custom session storage, simply pass the custom session instance to -``TelegramClient`` instead of the session name. +:ref:`TelegramClient ` instead of +the session name. Telethon contains two implementations of the abstract ``Session`` class: diff --git a/readthedocs/extra/basic/creating-a-client.rst b/readthedocs/extra/basic/creating-a-client.rst index 515d68ef..8b231ded 100644 --- a/readthedocs/extra/basic/creating-a-client.rst +++ b/readthedocs/extra/basic/creating-a-client.rst @@ -24,15 +24,15 @@ Once that's ready, the next step is to create a ``TelegramClient``. This class will be your main interface with Telegram's API, and creating one is very simple: - .. code-block:: python +.. code-block:: python - from telethon import TelegramClient + from telethon import TelegramClient - # Use your own values here - api_id = 12345 - api_hash = '0123456789abcdef0123456789abcdef' + # Use your own values here + api_id = 12345 + api_hash = '0123456789abcdef0123456789abcdef' - client = TelegramClient('some_name', api_id, api_hash) + client = TelegramClient('some_name', api_id, api_hash) Note that ``'some_name'`` will be used to save your session (persistent @@ -46,26 +46,44 @@ your disk. This is by default a database file using Python's ``sqlite3``. creates the file in your working directory, but absolute paths work too. +.. important:: + + The process shown here shows how to sign in *manually*. You **should** + use `client.start() ` instead + unless you have a better reason not to (e.g. you need more control): + + .. code-block:: python + + client.start() + + This is explained after going through the manual process. + + Before using the client, you must be connected to Telegram. Doing so is very easy: - ``client.connect() # Must return True, otherwise, try again`` +.. code-block:: python + + client.connect() # Must return True, otherwise, try again You may or may not be authorized yet. You must be authorized before you're able to send any request: - ``client.is_user_authorized() # Returns True if you can send requests`` +.. code-block:: python -If you're not authorized, you need to ``.sign_in()``: + client.is_user_authorized() # Returns True if you can send requests - .. code-block:: python +If you're not authorized, you need to `.sign_in +`: - phone_number = '+34600000000' - client.send_code_request(phone_number) - myself = client.sign_in(phone_number, input('Enter code: ')) - # If .sign_in raises PhoneNumberUnoccupiedError, use .sign_up instead - # If .sign_in raises SessionPasswordNeeded error, call .sign_in(password=...) - # You can import both exceptions from telethon.errors. +.. code-block:: python + + phone_number = '+34600000000' + client.send_code_request(phone_number) + myself = client.sign_in(phone_number, input('Enter code: ')) + # If .sign_in raises PhoneNumberUnoccupiedError, use .sign_up instead + # If .sign_in raises SessionPasswordNeeded error, call .sign_in(password=...) + # You can import both exceptions from telethon.errors. .. note:: @@ -82,24 +100,26 @@ mentioned ``.stringify()`` method, and printing these might prove useful. As a full example: - .. code-block:: python +.. code-block:: python - client = TelegramClient('anon', api_id, api_hash) - assert client.connect() - if not client.is_user_authorized(): - client.send_code_request(phone_number) - me = client.sign_in(phone_number, input('Enter code: ')) + client = TelegramClient('anon', api_id, api_hash) + assert client.connect() + if not client.is_user_authorized(): + client.send_code_request(phone_number) + me = client.sign_in(phone_number, input('Enter code: ')) -All of this, however, can be done through a call to ``.start()``: +All of this, however, can be done through a call to `.start() +`: - .. code-block:: python +.. code-block:: python - client = TelegramClient('anon', api_id, api_hash) - client.start() + client = TelegramClient('anon', api_id, api_hash) + client.start() -The code shown is just what ``.start()`` will be doing behind the scenes +The code shown is just what `.start() +` will be doing behind the scenes (with a few extra checks), so that you know how to sign in case you want to avoid using ``input()`` (the default) for whatever reason. If no phone or bot token is provided, you will be asked one through ``input()``. The @@ -108,25 +128,27 @@ method also accepts a ``phone=`` and ``bot_token`` parameters. You can use either, as both will work. Determining which is just a matter of taste, and how much control you need. -Remember that you can get yourself at any time with ``client.get_me()``. +Remember that you can get yourself at any time with `client.get_me() +`. .. warning:: Please note that if you fail to login around 5 times (or change the first - parameter of the ``TelegramClient``, which is the session name) you will - receive a ``FloodWaitError`` of around 22 hours, so be careful not to mess - this up! This shouldn't happen if you're doing things as explained, though. + parameter of the :ref:`TelegramClient `, which is the session + name) you will receive a ``FloodWaitError`` of around 22 hours, so be + careful not to mess this up! This shouldn't happen if you're doing things + as explained, though. .. note:: If you want to use a **proxy**, you have to `install PySocks`__ (via pip or manual) and then set the appropriated parameters: - .. code-block:: python + .. code-block:: python - import socks - client = TelegramClient('session_id', - api_id=12345, api_hash='0123456789abcdef0123456789abcdef', - proxy=(socks.SOCKS5, 'localhost', 4444) - ) + import socks + client = TelegramClient('session_id', + api_id=12345, api_hash='0123456789abcdef0123456789abcdef', + proxy=(socks.SOCKS5, 'localhost', 4444) + ) The ``proxy=`` argument should be a tuple, a list or a dict, consisting of parameters described `here`__. @@ -137,64 +159,69 @@ Two Factor Authorization (2FA) ****************************** If you have Two Factor Authorization (from now on, 2FA) enabled on your -account, calling :meth:`telethon.TelegramClient.sign_in` will raise a -``SessionPasswordNeededError``. When this happens, just -:meth:`telethon.TelegramClient.sign_in` again with a ``password=``: +account, calling `.sign_in() +` will raise a +``SessionPasswordNeededError``. When this happens, just use the method +again with a ``password=``: - .. code-block:: python +.. code-block:: python - import getpass - from telethon.errors import SessionPasswordNeededError + import getpass + from telethon.errors import SessionPasswordNeededError - client.sign_in(phone) - try: - client.sign_in(code=input('Enter code: ')) - except SessionPasswordNeededError: - client.sign_in(password=getpass.getpass()) + client.sign_in(phone) + try: + client.sign_in(code=input('Enter code: ')) + except SessionPasswordNeededError: + client.sign_in(password=getpass.getpass()) -The mentioned ``.start()`` method will handle this for you as well, but -you must set the ``password=`` parameter beforehand (it won't be asked). +The mentioned `.start() +` method will handle this for you as +well, but you must set the ``password=`` parameter beforehand (it won't be +asked). + +If you don't have 2FA enabled, but you would like to do so through the +library, use `client.edit_2fa() +`. -If you don't have 2FA enabled, but you would like to do so through the library, -use ``client.edit_2fa()``. Be sure to know what you're doing when using this function and -you won't run into any problems. -Take note that if you want to set only the email/hint and leave -the current password unchanged, you need to "redo" the 2fa. +you won't run into any problems. Take note that if you want to +set only the email/hint and leave the current password unchanged, +you need to "redo" the 2fa. See the examples below: - .. code-block:: python +.. code-block:: python - from telethon.errors import EmailUnconfirmedError - - # Sets 2FA password for first time: - client.edit_2fa(new_password='supersecurepassword') - - # Changes password: - client.edit_2fa(current_password='supersecurepassword', - new_password='changedmymind') - - # Clears current password (i.e. removes 2FA): - client.edit_2fa(current_password='changedmymind', new_password=None) - - # Sets new password with recovery email: - try: - client.edit_2fa(new_password='memes and dreams', - email='JohnSmith@example.com') - # Raises error (you need to check your email to complete 2FA setup.) - except EmailUnconfirmedError: - # You can put email checking code here if desired. - pass - - # Also take note that unless you remove 2FA or explicitly - # give email parameter again it will keep the last used setting - - # Set hint after already setting password: - client.edit_2fa(current_password='memes and dreams', - new_password='memes and dreams', - hint='It keeps you alive') + from telethon.errors import EmailUnconfirmedError + + # Sets 2FA password for first time: + client.edit_2fa(new_password='supersecurepassword') + + # Changes password: + client.edit_2fa(current_password='supersecurepassword', + new_password='changedmymind') + + # Clears current password (i.e. removes 2FA): + client.edit_2fa(current_password='changedmymind', new_password=None) + + # Sets new password with recovery email: + try: + client.edit_2fa(new_password='memes and dreams', + email='JohnSmith@example.com') + # Raises error (you need to check your email to complete 2FA setup.) + except EmailUnconfirmedError: + # You can put email checking code here if desired. + pass + + # Also take note that unless you remove 2FA or explicitly + # give email parameter again it will keep the last used setting + + # Set hint after already setting password: + client.edit_2fa(current_password='memes and dreams', + new_password='memes and dreams', + hint='It keeps you alive') __ https://github.com/Anorov/PySocks#installation __ https://github.com/Anorov/PySocks#usage-1 diff --git a/readthedocs/extra/basic/entities.rst b/readthedocs/extra/basic/entities.rst index 2e7d4a9a..3a160880 100644 --- a/readthedocs/extra/basic/entities.rst +++ b/readthedocs/extra/basic/entities.rst @@ -22,9 +22,9 @@ in response to certain methods, such as :tl:`GetUsersRequest`. To "encounter" an ID, you would have to "find it" like you would in the normal app. If the peer is in your dialogs, you would need to - `client.get_dialogs() `. + `client.get_dialogs() `. If the peer is someone in a group, you would similarly - `client.get_participants(group) `. + `client.get_participants(group) `. Once you have encountered an ID, the library will (by default) have saved their ``access_hash`` for you, which is needed to invoke most methods. @@ -69,9 +69,11 @@ you're able to just do this: my_channel = client.get_entity(PeerChannel(some_id)) -All methods in the :ref:`telegram-client` call ``.get_input_entity()`` prior +All methods in the :ref:`telegram-client` call `.get_input_entity() +` prior to sending the requst to save you from the hassle of doing so manually. -That way, convenience calls such as ``client.send_message('lonami', 'hi!')`` +That way, convenience calls such as `client.send_message('lonami', 'hi!') +` become possible. Every entity the library encounters (in any response to any call) will by @@ -88,8 +90,10 @@ Entities vs. Input Entities Don't worry if you don't understand this section, just remember some of the details listed here are important. When you're calling a method, - don't call ``.get_entity()`` beforehand, just use the username or phone, - or the entity retrieved by other means like ``.get_dialogs()``. + don't call `client.get_entity() ` + beforehand, just use the username or phone, or the entity retrieved by + other means like `client.get_dialogs() + `. On top of the normal types, the API also make use of what they call their @@ -108,21 +112,27 @@ before you can "use them". As we just mentioned, API calls don't need to know the whole information about the entities, only their ID and hash. For this reason, another method, -``.get_input_entity()`` is available. This will always use the cache while -possible, making zero API calls most of the time. When a request is made, -if you provided the full entity, e.g. an :tl:`User`, the library will convert -it to the required :tl:`InputPeer` automatically for you. +`client.get_input_entity() ` +is available. This will always use the cache while possible, making zero API +calls most of the time. When a request is made, if you provided the full +entity, e.g. an :tl:`User`, the library will convert it to the required +:tl:`InputPeer` automatically for you. -**You should always favour** ``.get_input_entity()`` **over** ``.get_entity()`` +**You should always favour** +`client.get_input_entity() ` +**over** +`client.get_entity() ` for this reason! Calling the latter will always make an API call to get the most recent information about said entity, but invoking requests don't -need this information, just the ``InputPeer``. Only use ``.get_entity()`` +need this information, just the :tl:`InputPeer`. Only use +`client.get_entity() ` if you need to get actual information, like the username, name, title, etc. of the entity. To further simplify the workflow, since the version ``0.16.2`` of the library, the raw requests you make to the API are also able to call -``.get_input_entity`` wherever needed, so you can even do things like: +`client.get_input_entity() ` +wherever needed, so you can even do things like: .. code-block:: python diff --git a/readthedocs/extra/basic/getting-started.rst b/readthedocs/extra/basic/getting-started.rst index 21830c21..9f14aa0a 100644 --- a/readthedocs/extra/basic/getting-started.rst +++ b/readthedocs/extra/basic/getting-started.rst @@ -9,83 +9,85 @@ Getting Started Simple Installation ******************* - ``pip3 install telethon`` +.. code-block:: sh - **More details**: :ref:`installation` + pip3 install telethon + +**More details**: :ref:`installation` Creating a client ***************** - .. code-block:: python +.. code-block:: python - from telethon import TelegramClient + from telethon import TelegramClient - # These example values won't work. You must get your own api_id and - # api_hash from https://my.telegram.org, under API Development. - api_id = 12345 - api_hash = '0123456789abcdef0123456789abcdef' + # These example values won't work. You must get your own api_id and + # api_hash from https://my.telegram.org, under API Development. + api_id = 12345 + api_hash = '0123456789abcdef0123456789abcdef' - client = TelegramClient('session_name', api_id, api_hash) - client.start() + client = TelegramClient('session_name', api_id, api_hash) + client.start() - **More details**: :ref:`creating-a-client` +**More details**: :ref:`creating-a-client` Basic Usage *********** - .. code-block:: python +.. code-block:: python - # Getting information about yourself - print(client.get_me().stringify()) + # Getting information about yourself + print(client.get_me().stringify()) - # Sending a message (you can use 'me' or 'self' to message yourself) - client.send_message('username', 'Hello World from Telethon!') + # Sending a message (you can use 'me' or 'self' to message yourself) + client.send_message('username', 'Hello World from Telethon!') - # Sending a file - client.send_file('username', '/home/myself/Pictures/holidays.jpg') + # Sending a file + client.send_file('username', '/home/myself/Pictures/holidays.jpg') - # Retrieving messages from a chat - from telethon import utils - for message in client.iter_messages('username', limit=10): - print(utils.get_display_name(message.sender), message.message) + # Retrieving messages from a chat + from telethon import utils + for message in client.iter_messages('username', limit=10): + print(utils.get_display_name(message.sender), message.message) - # Listing all the dialogs (conversations you have open) - for dialog in client.get_dialogs(limit=10): - print(utils.get_display_name(dialog.entity), dialog.draft.text) + # Listing all the dialogs (conversations you have open) + for dialog in client.get_dialogs(limit=10): + print(utils.get_display_name(dialog.entity), dialog.draft.text) - # Downloading profile photos (default path is the working directory) - client.download_profile_photo('username') + # Downloading profile photos (default path is the working directory) + client.download_profile_photo('username') - # Once you have a message with .media (if message.media) - # you can download it using client.download_media(): - messages = client.get_messages('username') - client.download_media(messages[0]) + # Once you have a message with .media (if message.media) + # you can download it using client.download_media(): + messages = client.get_messages('username') + client.download_media(messages[0]) - **More details**: :ref:`telegram-client` +**More details**: :ref:`telegram-client` - See :ref:`telethon-client` for all available friendly methods. +See :ref:`telethon-client` for all available friendly methods. Handling Updates **************** - .. code-block:: python +.. code-block:: python - from telethon import events + from telethon import events - # We need to have some worker running - client.updates.workers = 1 + # We need to have some worker running + client.updates.workers = 1 - @client.on(events.NewMessage(incoming=True, pattern='(?i)hi')) - def handler(event): - event.reply('Hello!') + @client.on(events.NewMessage(incoming=True, pattern='(?i)hi')) + def handler(event): + event.reply('Hello!') - # If you want to handle updates you can't let the script end. - input('Press enter to exit.') + # If you want to handle updates you can't let the script end. + input('Press enter to exit.') - **More details**: :ref:`working-with-updates` +**More details**: :ref:`working-with-updates` ---------- diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst index c8182dbb..983472b5 100644 --- a/readthedocs/extra/basic/installation.rst +++ b/readthedocs/extra/basic/installation.rst @@ -10,24 +10,28 @@ Automatic Installation To install Telethon, simply do: - ``pip3 install telethon`` +.. code-block:: sh + + pip3 install telethon Needless to say, you must have Python 3 and PyPi installed in your system. See https://python.org and https://pypi.python.org/pypi/pip for more. If you already have the library installed, upgrade with: - ``pip3 install --upgrade telethon`` +.. code-block:: sh + + pip3 install --upgrade telethon You can also install the library directly from GitHub or a fork: - .. code-block:: sh +.. code-block:: sh - # pip3 install git+https://github.com/LonamiWebs/Telethon.git - or - $ git clone https://github.com/LonamiWebs/Telethon.git - $ cd Telethon/ - # pip install -Ue . + # pip3 install git+https://github.com/LonamiWebs/Telethon.git + or + $ git clone https://github.com/LonamiWebs/Telethon.git + $ cd Telethon/ + # pip install -Ue . If you don't have root access, simply pass the ``--user`` flag to the pip command. If you want to install a specific branch, append ``@branch`` to @@ -38,7 +42,9 @@ which can be really slow when uploading or downloading files. If you don't mind using a C extension, install `cryptg `__ via ``pip`` or as an extra: - ``pip3 install telethon[cryptg]`` +.. code-block:: sh + + pip3 install telethon[cryptg] Manual Installation @@ -47,14 +53,27 @@ Manual Installation 1. Install the required ``pyaes`` (`GitHub`__ | `PyPi`__) and ``rsa`` (`GitHub`__ | `PyPi`__) modules: - ``sudo -H pip3 install pyaes rsa`` + .. code-block:: sh + + pip3 install pyaes rsa 2. Clone Telethon's GitHub repository: - ``git clone https://github.com/LonamiWebs/Telethon.git`` -3. Enter the cloned repository: ``cd Telethon`` + .. code-block:: sh -4. Run the code generator: ``python3 setup.py gen tl errors`` + git clone https://github.com/LonamiWebs/Telethon.git + +3. Enter the cloned repository: + + .. code-block:: sh + + cd Telethon + +4. Run the code generator: + + .. code-block:: sh + + python3 setup.py gen 5. Done! diff --git a/readthedocs/extra/basic/telegram-client.rst b/readthedocs/extra/basic/telegram-client.rst index 273637ed..17ff3bb4 100644 --- a/readthedocs/extra/basic/telegram-client.rst +++ b/readthedocs/extra/basic/telegram-client.rst @@ -15,9 +15,9 @@ Introduction available methods are in the :ref:`telethon-client` reference, including detailed descriptions to what they do. -The ``TelegramClient`` is the central class of the library, the one -you will be using most of the time. For this reason, it's important -to know what it offers. +The :ref:`TelegramClient ` is the +central class of the library, the one you will be using most of the time. For +this reason, it's important to know what it offers. Since we're working with Python, one must not forget that we can do ``help(client)`` or ``help(TelegramClient)`` at any time for a more @@ -27,12 +27,14 @@ methods for any object, even yours! Interacting with the Telegram API is done through sending **requests**, this is, any "method" listed on the API. There are a few methods (and -growing!) on the ``TelegramClient`` class that abstract you from the -need of manually importing the requests you need. +growing!) on the :ref:`TelegramClient ` class that abstract +you from the need of manually importing the requests you need. For instance, retrieving your own user can be done in a single line: - ``myself = client.get_me()`` +.. code-block:: python + + myself = client.get_me() Internally, this method has sent a request to Telegram, who replied with the information about your own user, and then the desired information @@ -42,12 +44,12 @@ If you want to retrieve any other user, chat or channel (channels are a special subset of chats), you want to retrieve their "entity". This is how the library refers to either of these: - .. code-block:: python +.. code-block:: python - # The method will infer that you've passed an username - # It also accepts phone numbers, and will get the user - # from your contact list. - lonami = client.get_entity('lonami') + # The method will infer that you've passed an username + # It also accepts phone numbers, and will get the user + # from your contact list. + lonami = client.get_entity('lonami') The so called "entities" are another important whole concept on its own, but for now you don't need to worry about it. Simply know that they are @@ -55,32 +57,32 @@ a good way to get information about an user, chat or channel. Many other common methods for quick scripts are also available: - .. code-block:: python +.. code-block:: python - # Note that you can use 'me' or 'self' to message yourself - client.send_message('username', 'Hello World from Telethon!') + # Note that you can use 'me' or 'self' to message yourself + client.send_message('username', 'Hello World from Telethon!') - # .send_message's parse mode defaults to markdown, so you - # can use **bold**, __italics__, [links](https://example.com), `code`, - # and even [mentions](@username)/[mentions](tg://user?id=123456789) - client.send_message('username', '**Using** __markdown__ `too`!') + # .send_message's parse mode defaults to markdown, so you + # can use **bold**, __italics__, [links](https://example.com), `code`, + # and even [mentions](@username)/[mentions](tg://user?id=123456789) + client.send_message('username', '**Using** __markdown__ `too`!') - client.send_file('username', '/home/myself/Pictures/holidays.jpg') + client.send_file('username', '/home/myself/Pictures/holidays.jpg') - # The utils package has some goodies, like .get_display_name() - from telethon import utils - for message in client.iter_messages('username', limit=10): - print(utils.get_display_name(message.sender), message.message) + # The utils package has some goodies, like .get_display_name() + from telethon import utils + for message in client.iter_messages('username', limit=10): + print(utils.get_display_name(message.sender), message.message) - # Dialogs are the conversations you have open - for dialog in client.get_dialogs(limit=10): - print(utils.get_display_name(dialog.entity), dialog.draft.text) + # Dialogs are the conversations you have open + for dialog in client.get_dialogs(limit=10): + print(utils.get_display_name(dialog.entity), dialog.draft.text) - # Default path is the working directory - client.download_profile_photo('username') + # Default path is the working directory + client.download_profile_photo('username') - # Call .disconnect() when you're done - client.disconnect() + # Call .disconnect() when you're done + client.disconnect() Remember that you can call ``.stringify()`` to any object Telegram returns to pretty print it. Calling ``str(result)`` does the same operation, but on @@ -91,9 +93,9 @@ Available methods ***************** The :ref:`reference ` lists all the "handy" methods -available for you to use in the ``TelegramClient`` class. These are simply -wrappers around the "raw" Telegram API, making it much more manageable and -easier to work with. +available for you to use in the :ref:`TelegramClient ` class. +These are simply wrappers around the "raw" Telegram API, making it much more +manageable and easier to work with. Please refer to :ref:`accessing-the-full-api` if these aren't enough, and don't be afraid to read the source code of the InteractiveTelegramClient_ diff --git a/readthedocs/extra/basic/working-with-updates.rst b/readthedocs/extra/basic/working-with-updates.rst index 308c3a79..8d1b3e50 100644 --- a/readthedocs/extra/basic/working-with-updates.rst +++ b/readthedocs/extra/basic/working-with-updates.rst @@ -5,21 +5,27 @@ Working with Updates ==================== -The library comes with the :mod:`events` module. *Events* are an abstraction +The library comes with the `telethon.events` module. *Events* are an abstraction over what Telegram calls `updates`__, and are meant to ease simple and common usage when dealing with them, since there are many updates. If you're looking for the method reference, check :ref:`telethon-events-package`, otherwise, let's dive in! -.. note:: +.. important:: The library logs by default no output, and any exception that occurs inside your handlers will be "hidden" from you to prevent the thread from terminating (so it can still deliver events). You should enable - logging (``import logging; logging.basicConfig(level=logging.ERROR)``) - when working with events, at least the error level, to see if this is - happening so you can debug the error. + logging when working with events, at least the error level, to see if + this is happening so you can debug the error. + + **When using updates, please enable logging:** + + .. code-block:: python + + import logging + logging.basicConfig(level=logging.ERROR) .. contents:: @@ -62,7 +68,8 @@ Nothing we don't know already. This Python decorator will attach itself to the ``my_event_handler`` -definition, and basically means that *on* a ``NewMessage`` *event*, +definition, and basically means that *on* a `NewMessage +` *event*, the callback function you're about to define will be called: .. code-block:: python @@ -72,8 +79,10 @@ the callback function you're about to define will be called: event.reply('hi!') -If a ``NewMessage`` event occurs, and ``'hello'`` is in the text of the -message, we ``reply`` to the event with a ``'hi!'`` message. +If a `NewMessage +` event occurs, +and ``'hello'`` is in the text of the message, we ``reply`` to the event +with a ``'hi!'`` message. .. code-block:: python @@ -88,10 +97,11 @@ do other things instead idling. For this refer to :ref:`update-modes`. More on events ************** -The ``NewMessage`` event has much more than what was shown. You can access -the ``.sender`` of the message through that member, or even see if the message -had ``.media``, a ``.photo`` or a ``.document`` (which you could download with -for example ``client.download_media(event.photo)``. +The `NewMessage ` event has much +more than what was shown. You can access the ``.sender`` of the message +through that member, or even see if the message had ``.media``, a ``.photo`` +or a ``.document`` (which you could download with for example +`client.download_media(event.photo) `. If you don't want to ``.reply`` as a reply, you can use the ``.respond()`` method instead. Of course, there are more events such as ``ChatAction`` or @@ -102,34 +112,35 @@ instance, ``NewMessage.Event``), except for the ``Raw`` event which just passes the ``Update`` object. Note that ``.reply()`` and ``.respond()`` are just wrappers around the -``client.send_message()`` method which supports the ``file=`` parameter. -This means you can reply with a photo if you do ``client.reply(file=photo)``. +`client.send_message() ` +method which supports the ``file=`` parameter. +This means you can reply with a photo if you do ``event.reply(file=photo)``. You can put the same event on many handlers, and even different events on the same handler. You can also have a handler work on only specific chats, for example: - .. code-block:: python +.. code-block:: python - import ast - import random + import ast + import random - # Either a single item or a list of them will work for the chats. - # You can also use the IDs, Peers, or even User/Chat/Channel objects. - @client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic'))) - def normal_handler(event): - if 'roll' in event.raw_text: - event.reply(str(random.randint(1, 6))) + # Either a single item or a list of them will work for the chats. + # You can also use the IDs, Peers, or even User/Chat/Channel objects. + @client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic'))) + def normal_handler(event): + if 'roll' in event.raw_text: + event.reply(str(random.randint(1, 6))) - # Similarly, you can use incoming=True for messages that you receive - @client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True)) - def admin_handler(event): - if event.raw_text.startswith('eval'): - expression = event.raw_text.replace('eval', '').strip() - event.reply(str(ast.literal_eval(expression))) + # Similarly, you can use incoming=True for messages that you receive + @client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True)) + def admin_handler(event): + if event.raw_text.startswith('eval'): + expression = event.raw_text.replace('eval', '').strip() + event.reply(str(ast.literal_eval(expression))) You can pass one or more chats to the ``chats`` parameter (as a list or tuple), @@ -143,15 +154,20 @@ solution. Try it! Events without decorators ************************* -If for any reason you can't use the ``@client.on`` syntax, don't worry. -You can call ``client.add_event_handler(callback, event)`` to achieve +If for any reason you can't use the `@client.on +` syntax, don't worry. +You can call `client.add_event_handler(callback, event) +` to achieve the same effect. -Similar to that method, you also have :meth:`client.remove_event_handler` -and :meth:`client.list_event_handlers` which do as they names indicate. +Similarly, you also have `client.remove_event_handler +` +and `client.list_event_handlers +`. -The ``event`` type is optional in all methods and defaults to ``events.Raw`` -for adding, and ``None`` when removing (so all callbacks would be removed). +The ``event`` type is optional in all methods and defaults to +`events.Raw ` for adding, and ``None`` when +removing (so all callbacks would be removed). Stopping propagation of Updates @@ -159,8 +175,8 @@ Stopping propagation of Updates There might be cases when an event handler is supposed to be used solitary and it makes no sense to process any other handlers in the chain. For this case, -it is possible to raise a ``StopPropagation`` exception which will cause the -propagation of the update through your handlers to stop: +it is possible to raise a `telethon.events.StopPropagation` exception which +will cause the propagation of the update through your handlers to stop: .. code-block:: python diff --git a/readthedocs/extra/developing/project-structure.rst b/readthedocs/extra/developing/project-structure.rst index c745d6d6..1bbb07c4 100644 --- a/readthedocs/extra/developing/project-structure.rst +++ b/readthedocs/extra/developing/project-structure.rst @@ -12,27 +12,32 @@ that servers as a nice interface with the most commonly used methods on Telegram such as sending messages, retrieving the message history, handling updates, etc. -The ``TelegramClient`` inherits the ``TelegramBareClient``. The later is -basically a pruned version of the ``TelegramClient``, which knows basic -stuff like ``.invoke()``\ 'ing requests, downloading files, or switching -between data centers. This is primary to keep the method count per class -and file low and manageable. +The ``TelegramClient`` inherits from several mixing ``Method`` classes, +since there are so many methods that having them in a single file would +make maintenance painful (it was three thousand lines before this separation +happened!). It's a "god object", but there is only a way to interact with +Telegram really. -Both clients make use of the ``network/mtproto_sender.py``. The -``MtProtoSender`` class handles packing requests with the ``salt``, -``id``, ``sequence``, etc., and also handles how to process responses -(i.e. pong, RPC errors). This class communicates through Telegram via -its ``.connection`` member. +The ``TelegramBaseClient`` is an ABC which will support all of these mixins +so they can work together nicely. It doesn't even know how to invoke things +because they need to be resolved with user information first (to work with +input entities comfortably). -The ``Connection`` class uses a ``extensions/tcp_client``, a C#-like -``TcpClient`` to ease working with sockets in Python. All the +The client makes use of the ``network/mtprotosender.py``. The +``MTProtoSender`` is responsible for connecting, reconnecting, +packing, unpacking, sending and receiving items from the network. +Basically, the low-level communication with Telegram, and handling +MTProto-related functions and types such as ``BadSalt``. + +The sender makes use of a ``Connection`` class which knows the format in +which outgoing messages should be sent (how to encode their length and +their body, if they're further encrypted). + +For now, all connection modes make use of the ``extensions/tcpclient``, +a C#-like ``TcpClient`` to ease working with sockets in Python. All the ``TcpClient`` know is how to connect through TCP and writing/reading from the socket with optional cancel. -The ``Connection`` class bundles up all the connections modes and sends -and receives the messages accordingly (TCP full, obfuscated, -intermediate…). - Auto-generated code ******************* diff --git a/readthedocs/extra/developing/test-servers.rst b/readthedocs/extra/developing/test-servers.rst index a3288a25..89a5d734 100644 --- a/readthedocs/extra/developing/test-servers.rst +++ b/readthedocs/extra/developing/test-servers.rst @@ -5,10 +5,10 @@ Test Servers To run Telethon on a test server, use the following code: - .. code-block:: python +.. code-block:: python - client = TelegramClient(None, api_id, api_hash) - client.session.set_dc(dc_id, '149.154.167.40', 80) + client = TelegramClient(None, api_id, api_hash) + client.session.set_dc(dc_id, '149.154.167.40', 80) You can check your ``'test ip'`` on https://my.telegram.org. @@ -28,8 +28,8 @@ Valid phone numbers are ``99966XYYYY``, where ``X`` is the ``dc_id`` and be ``9996621234``. The code sent by Telegram will be ``dc_id`` repeated five times, in this case, ``22222`` so we can hardcode that: - .. code-block:: python +.. code-block:: python - client = TelegramClient(None, api_id, api_hash) - client.session.set_dc(2, '149.154.167.40', 80) - client.start(phone='9996621234', code_callback=lambda: '22222') + client = TelegramClient(None, api_id, api_hash) + client.session.set_dc(2, '149.154.167.40', 80) + client.start(phone='9996621234', code_callback=lambda: '22222') diff --git a/readthedocs/extra/developing/tips-for-porting-the-project.rst b/readthedocs/extra/developing/tips-for-porting-the-project.rst index c7135096..69348f9d 100644 --- a/readthedocs/extra/developing/tips-for-porting-the-project.rst +++ b/readthedocs/extra/developing/tips-for-porting-the-project.rst @@ -8,7 +8,7 @@ be kind and don't forget to mention it helped you! You should start by reading the source code on the `first release `__ of -the project, and start creating a ``MtProtoSender``. Once this is made, +the project, and start creating a ``MTProtoSender``. Once this is made, you should write by hand the code to authenticate on the Telegram's server, which are some steps required to get the key required to talk to them. Save it somewhere! Then, simply mimic, or reinvent other parts of diff --git a/readthedocs/extra/examples/bots.rst b/readthedocs/extra/examples/bots.rst index 536d239a..0e1a9122 100644 --- a/readthedocs/extra/examples/bots.rst +++ b/readthedocs/extra/examples/bots.rst @@ -15,26 +15,26 @@ You can query an inline bot, such as `@VoteBot`__ (note, *query*, not *interact* with a voting message), by making use of the :tl:`GetInlineBotResultsRequest` request: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.messages import GetInlineBotResultsRequest + from telethon.tl.functions.messages import GetInlineBotResultsRequest - bot_results = client(GetInlineBotResultsRequest( - bot, user_or_chat, 'query', '' - )) + bot_results = client(GetInlineBotResultsRequest( + bot, user_or_chat, 'query', '' + )) And you can select any of their results by using :tl:`SendInlineBotResultRequest`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.messages import SendInlineBotResultRequest + from telethon.tl.functions.messages import SendInlineBotResultRequest - client(SendInlineBotResultRequest( - get_input_peer(user_or_chat), - obtained_query_id, - obtained_str_id - )) + client(SendInlineBotResultRequest( + get_input_peer(user_or_chat), + obtained_query_id, + obtained_str_id + )) Talking to Bots with special reply markup @@ -43,15 +43,15 @@ Talking to Bots with special reply markup To interact with a message that has a special reply markup, such as `@VoteBot`__ polls, you would use :tl:`GetBotCallbackAnswerRequest`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.messages import GetBotCallbackAnswerRequest + from telethon.tl.functions.messages import GetBotCallbackAnswerRequest - client(GetBotCallbackAnswerRequest( - user_or_chat, - msg.id, - data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data - )) + client(GetBotCallbackAnswerRequest( + user_or_chat, + msg.id, + data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data + )) It's a bit verbose, but it has all the information you would need to show it visually (button rows, and buttons within each row, each with diff --git a/readthedocs/extra/examples/chats-and-channels.rst b/readthedocs/extra/examples/chats-and-channels.rst index 9722c3dc..847d8462 100644 --- a/readthedocs/extra/examples/chats-and-channels.rst +++ b/readthedocs/extra/examples/chats-and-channels.rst @@ -22,14 +22,14 @@ Joining a public channel Once you have the :ref:`entity ` of the channel you want to join to, you can make use of the :tl:`JoinChannelRequest` to join such channel: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.channels import JoinChannelRequest - client(JoinChannelRequest(channel)) + from telethon.tl.functions.channels import JoinChannelRequest + client(JoinChannelRequest(channel)) - # In the same way, you can also leave such channel - from telethon.tl.functions.channels import LeaveChannelRequest - client(LeaveChannelRequest(input_channel)) + # In the same way, you can also leave such channel + from telethon.tl.functions.channels import LeaveChannelRequest + client(LeaveChannelRequest(input_channel)) For more on channels, check the `channels namespace`__. @@ -48,10 +48,10 @@ enough information to join! The part after the example, is the ``hash`` of the chat or channel. Now you can use :tl:`ImportChatInviteRequest` as follows: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.messages import ImportChatInviteRequest - updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) + from telethon.tl.functions.messages import ImportChatInviteRequest + updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) Adding someone else to such chat or channel @@ -61,26 +61,26 @@ If you don't want to add yourself, maybe because you're already in, you can always add someone else with the :tl:`AddChatUserRequest`, which use is very straightforward, or :tl:`InviteToChannelRequest` for channels: - .. code-block:: python +.. code-block:: python - # For normal chats - from telethon.tl.functions.messages import AddChatUserRequest + # For normal chats + from telethon.tl.functions.messages import AddChatUserRequest - # Note that ``user_to_add`` is NOT the name of the parameter. - # It's the user you want to add (``user_id=user_to_add``). - client(AddChatUserRequest( - chat_id, - user_to_add, - fwd_limit=10 # Allow the user to see the 10 last messages - )) + # Note that ``user_to_add`` is NOT the name of the parameter. + # It's the user you want to add (``user_id=user_to_add``). + client(AddChatUserRequest( + chat_id, + user_to_add, + fwd_limit=10 # Allow the user to see the 10 last messages + )) - # For channels (which includes megagroups) - from telethon.tl.functions.channels import InviteToChannelRequest + # For channels (which includes megagroups) + from telethon.tl.functions.channels import InviteToChannelRequest - client(InviteToChannelRequest( - channel, - [users_to_add] - )) + client(InviteToChannelRequest( + channel, + [users_to_add] + )) Checking a link without joining @@ -112,25 +112,25 @@ closest thing to "no filter" is to simply use If we want to get *all* the members, we need to use a moving offset and a fixed limit: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.channels import GetParticipantsRequest - from telethon.tl.types import ChannelParticipantsSearch - from time import sleep + from telethon.tl.functions.channels import GetParticipantsRequest + from telethon.tl.types import ChannelParticipantsSearch + from time import sleep - offset = 0 - limit = 100 - all_participants = [] + offset = 0 + limit = 100 + all_participants = [] - while True: - participants = client(GetParticipantsRequest( - channel, ChannelParticipantsSearch(''), offset, limit, - hash=0 - )) - if not participants.users: - break - all_participants.extend(participants.users) - offset += len(participants.users) + while True: + participants = client(GetParticipantsRequest( + channel, ChannelParticipantsSearch(''), offset, limit, + hash=0 + )) + if not participants.users: + break + all_participants.extend(participants.users) + offset += len(participants.users) .. note:: @@ -164,39 +164,39 @@ Admin Permissions Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.channels import EditAdminRequest - from telethon.tl.types import ChannelAdminRights + from telethon.tl.functions.channels import EditAdminRequest + from telethon.tl.types import ChannelAdminRights - # You need both the channel and who to grant permissions - # They can either be channel/user or input channel/input user. - # - # ChannelAdminRights is a list of granted permissions. - # Set to True those you want to give. - rights = ChannelAdminRights( - post_messages=None, - add_admins=None, - invite_users=None, - change_info=True, - ban_users=None, - delete_messages=True, - pin_messages=True, - invite_link=None, - edit_messages=None - ) - # Equivalent to: - # rights = ChannelAdminRights( - # change_info=True, - # delete_messages=True, - # pin_messages=True - # ) + # You need both the channel and who to grant permissions + # They can either be channel/user or input channel/input user. + # + # ChannelAdminRights is a list of granted permissions. + # Set to True those you want to give. + rights = ChannelAdminRights( + post_messages=None, + add_admins=None, + invite_users=None, + change_info=True, + ban_users=None, + delete_messages=True, + pin_messages=True, + invite_link=None, + edit_messages=None + ) + # Equivalent to: + # rights = ChannelAdminRights( + # change_info=True, + # delete_messages=True, + # pin_messages=True + # ) - # Once you have a ChannelAdminRights, invoke it - client(EditAdminRequest(channel, user, rights)) + # Once you have a ChannelAdminRights, invoke it + client(EditAdminRequest(channel, user, rights)) - # User will now be able to change group info, delete other people's - # messages and pin messages. + # User will now be able to change group info, delete other people's + # messages and pin messages. .. note:: @@ -218,41 +218,41 @@ Similar to how you give or revoke admin permissions, you can edit the banned rights of an user through :tl:`EditBannedRequest` and its parameter :tl:`ChannelBannedRights`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.channels import EditBannedRequest - from telethon.tl.types import ChannelBannedRights + from telethon.tl.functions.channels import EditBannedRequest + from telethon.tl.types import ChannelBannedRights - from datetime import datetime, timedelta + from datetime import datetime, timedelta - # Restricting an user for 7 days, only allowing view/send messages. - # - # Note that it's "reversed". You must set to ``True`` the permissions - # you want to REMOVE, and leave as ``None`` those you want to KEEP. - rights = ChannelBannedRights( - until_date=datetime.now() + timedelta(days=7), - view_messages=None, - send_messages=None, - send_media=True, - send_stickers=True, - send_gifs=True, - send_games=True, - send_inline=True, - embed_links=True - ) + # Restricting an user for 7 days, only allowing view/send messages. + # + # Note that it's "reversed". You must set to ``True`` the permissions + # you want to REMOVE, and leave as ``None`` those you want to KEEP. + rights = ChannelBannedRights( + until_date=datetime.now() + timedelta(days=7), + view_messages=None, + send_messages=None, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True + ) - # The above is equivalent to - rights = ChannelBannedRights( - until_date=datetime.now() + timedelta(days=7), - send_media=True, - send_stickers=True, - send_gifs=True, - send_games=True, - send_inline=True, - embed_links=True - ) + # The above is equivalent to + rights = ChannelBannedRights( + until_date=datetime.now() + timedelta(days=7), + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True + ) - client(EditBannedRequest(channel, user, rights)) + client(EditBannedRequest(channel, user, rights)) Kicking a member @@ -262,15 +262,15 @@ Telegram doesn't actually have a request to kick an user from a group. Instead, you need to restrict them so they can't see messages. Any date is enough: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.channels import EditBannedRequest - from telethon.tl.types import ChannelBannedRights + from telethon.tl.functions.channels import EditBannedRequest + from telethon.tl.types import ChannelBannedRights - client(EditBannedRequest(channel, user, ChannelBannedRights( - until_date=None, - view_messages=True - ))) + client(EditBannedRequest(channel, user, ChannelBannedRights( + until_date=None, + view_messages=True + ))) __ https://github.com/Kyle2142 @@ -285,17 +285,17 @@ It has been asked `quite`__ `a few`__ `times`__ (really, `many`__), and while I don't understand why so many people ask this, the solution is to use :tl:`GetMessagesViewsRequest`, setting ``increment=True``: - .. code-block:: python +.. code-block:: python - # Obtain `channel' through dialogs or through client.get_entity() or anyhow. - # Obtain `msg_ids' through `.get_messages()` or anyhow. Must be a list. + # Obtain `channel' through dialogs or through client.get_entity() or anyhow. + # Obtain `msg_ids' through `.get_messages()` or anyhow. Must be a list. - client(GetMessagesViewsRequest( - peer=channel, - id=msg_ids, - increment=True - )) + client(GetMessagesViewsRequest( + peer=channel, + id=msg_ids, + increment=True + )) Note that you can only do this **once or twice a day** per account, diff --git a/readthedocs/extra/examples/users.rst b/readthedocs/extra/examples/users.rst index 1d931ebf..a327e9a0 100644 --- a/readthedocs/extra/examples/users.rst +++ b/readthedocs/extra/examples/users.rst @@ -15,15 +15,15 @@ If you need to retrieve the bio, biography or about information for an user you should use :tl:`GetFullUser`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.users import GetFullUserRequest + from telethon.tl.functions.users import GetFullUserRequest - full = client(GetFullUserRequest(user)) - # or even - full = client(GetFullUserRequest('username')) + full = client(GetFullUserRequest(user)) + # or even + full = client(GetFullUserRequest('username')) - bio = full.about + bio = full.about See :tl:`UserFull` to know what other fields you can access. @@ -35,11 +35,11 @@ Updating your name and/or bio The first name, last name and bio (about) can all be changed with the same request. Omitted fields won't change after invoking :tl:`UpdateProfile`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.account import UpdateProfileRequest + from telethon.tl.functions.account import UpdateProfileRequest - client(UpdateProfileRequest(about='This is a test from Telethon')) + client(UpdateProfileRequest(about='This is a test from Telethon')) Updating your username @@ -47,11 +47,11 @@ Updating your username You need to use :tl:`account.UpdateUsername`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.account import UpdateUsernameRequest + from telethon.tl.functions.account import UpdateUsernameRequest - client(UpdateUsernameRequest('new_username')) + client(UpdateUsernameRequest('new_username')) Updating your profile photo @@ -61,10 +61,10 @@ The easiest way is to upload a new file and use that as the profile photo through :tl:`UploadProfilePhoto`: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.photos import UploadProfilePhotoRequest + from telethon.tl.functions.photos import UploadProfilePhotoRequest - client(UploadProfilePhotoRequest( - client.upload_file('/path/to/some/file') - )) + client(UploadProfilePhotoRequest( + client.upload_file('/path/to/some/file') + )) diff --git a/readthedocs/extra/examples/working-with-messages.rst b/readthedocs/extra/examples/working-with-messages.rst index 0571beef..42e6a87d 100644 --- a/readthedocs/extra/examples/working-with-messages.rst +++ b/readthedocs/extra/examples/working-with-messages.rst @@ -20,31 +20,31 @@ Forwarding messages .. code-block:: python - # If you only have the message IDs - client.forward_messages( - entity, # to which entity you are forwarding the messages - message_ids, # the IDs of the messages (or message) to forward - from_entity # who sent the messages? - ) + # If you only have the message IDs + client.forward_messages( + entity, # to which entity you are forwarding the messages + message_ids, # the IDs of the messages (or message) to forward + from_entity # who sent the messages? + ) - # If you have ``Message`` objects - client.forward_messages( - entity, # to which entity you are forwarding the messages - messages # the messages (or message) to forward - ) + # If you have ``Message`` objects + client.forward_messages( + entity, # to which entity you are forwarding the messages + messages # the messages (or message) to forward + ) - # You can also do it manually if you prefer - from telethon.tl.functions.messages import ForwardMessagesRequest + # You can also do it manually if you prefer + from telethon.tl.functions.messages import ForwardMessagesRequest - messages = foo() # retrieve a few messages (or even one, in a list) - from_entity = bar() - to_entity = baz() + messages = foo() # retrieve a few messages (or even one, in a list) + from_entity = bar() + to_entity = baz() - client(ForwardMessagesRequest( - from_peer=from_entity, # who sent these messages? - id=[msg.id for msg in messages], # which are the messages? - to_peer=to_entity # who are we forwarding them to? - )) + client(ForwardMessagesRequest( + from_peer=from_entity, # who sent these messages? + id=[msg.id for msg in messages], # which are the messages? + to_peer=to_entity # who are we forwarding them to? + )) The named arguments are there for clarity, although they're not needed because they appear in order. You can obviously just wrap a single message on the list @@ -65,26 +65,26 @@ Searching Messages Messages are searched through the obvious :tl:`SearchRequest`, but you may run into issues_. A valid example would be: - .. code-block:: python +.. code-block:: python - from telethon.tl.functions.messages import SearchRequest - from telethon.tl.types import InputMessagesFilterEmpty + from telethon.tl.functions.messages import SearchRequest + from telethon.tl.types import InputMessagesFilterEmpty - filter = InputMessagesFilterEmpty() - result = client(SearchRequest( - peer=peer, # On which chat/conversation - q='query', # What to search for - filter=filter, # Filter to use (maybe filter for media) - min_date=None, # Minimum date - max_date=None, # Maximum date - offset_id=0, # ID of the message to use as offset - add_offset=0, # Additional offset - limit=10, # How many results - max_id=0, # Maximum message ID - min_id=0, # Minimum message ID - from_id=None, # Who must have sent the message (peer) - hash=0 # Special number to return nothing on no-change - )) + filter = InputMessagesFilterEmpty() + result = client(SearchRequest( + peer=peer, # On which chat/conversation + q='query', # What to search for + filter=filter, # Filter to use (maybe filter for media) + min_date=None, # Minimum date + max_date=None, # Maximum date + offset_id=0, # ID of the message to use as offset + add_offset=0, # Additional offset + limit=10, # How many results + max_id=0, # Maximum message ID + min_id=0, # Minimum message ID + from_id=None, # Who must have sent the message (peer) + hash=0 # Special number to return nothing on no-change + )) It's important to note that the optional parameter ``from_id`` could have been omitted (defaulting to ``None``). Changing it to :tl:`InputUserEmpty`, as one @@ -113,31 +113,31 @@ referenced through this pair of ID/hash (unique per user), and you need to use this handle when sending a "document" message. This working example will send yourself the very first sticker you have: - .. code-block:: python +.. code-block:: python - # Get all the sticker sets this user has - sticker_sets = client(GetAllStickersRequest(0)) + # Get all the sticker sets this user has + sticker_sets = client(GetAllStickersRequest(0)) - # Choose a sticker set - sticker_set = sticker_sets.sets[0] + # Choose a sticker set + sticker_set = sticker_sets.sets[0] - # Get the stickers for this sticker set - stickers = client(GetStickerSetRequest( - stickerset=InputStickerSetID( - id=sticker_set.id, access_hash=sticker_set.access_hash + # Get the stickers for this sticker set + stickers = client(GetStickerSetRequest( + stickerset=InputStickerSetID( + id=sticker_set.id, access_hash=sticker_set.access_hash + ) + )) + + # Stickers are nothing more than files, so send that + client(SendMediaRequest( + peer=client.get_me(), + media=InputMediaDocument( + id=InputDocument( + id=stickers.documents[0].id, + access_hash=stickers.documents[0].access_hash ) - )) - - # Stickers are nothing more than files, so send that - client(SendMediaRequest( - peer=client.get_me(), - media=InputMediaDocument( - id=InputDocument( - id=stickers.documents[0].id, - access_hash=stickers.documents[0].access_hash - ) - ) - )) + ) + )) .. _issues: https://github.com/LonamiWebs/Telethon/issues/215 diff --git a/readthedocs/extra/troubleshooting/enable-logging.rst b/readthedocs/extra/troubleshooting/enable-logging.rst index 897052e2..8d8747f4 100644 --- a/readthedocs/extra/troubleshooting/enable-logging.rst +++ b/readthedocs/extra/troubleshooting/enable-logging.rst @@ -14,25 +14,25 @@ will be printed unless you explicitly enable it. You can also `use the module`__ on your own project very easily: - .. code-block:: python +.. code-block:: python - import logging - logger = logging.getLogger(__name__) + import logging + logger = logging.getLogger(__name__) - logger.debug('Debug messages') - logger.info('Useful information') - logger.warning('This is a warning!') + logger.debug('Debug messages') + logger.info('Useful information') + logger.warning('This is a warning!') If you want to enable ``logging`` for your project *but* use a different log level for the library: - .. code-block:: python +.. code-block:: python - import logging - logging.basicConfig(level=logging.DEBUG) - # For instance, show only warnings and above - logging.getLogger('telethon').setLevel(level=logging.WARNING) + import logging + logging.basicConfig(level=logging.DEBUG) + # For instance, show only warnings and above + logging.getLogger('telethon').setLevel(level=logging.WARNING) __ https://docs.python.org/3/library/logging.html diff --git a/readthedocs/extra/wall-of-shame.rst b/readthedocs/extra/wall-of-shame.rst index 4f7b5660..83e96956 100644 --- a/readthedocs/extra/wall-of-shame.rst +++ b/readthedocs/extra/wall-of-shame.rst @@ -50,14 +50,14 @@ The current winner is `issue **Issue:** - .. figure:: https://user-images.githubusercontent.com/6297805/29822978-9a9a6ef0-8ccd-11e7-9ec5-934ea0f57681.jpg - :alt: Winner issue +.. figure:: https://user-images.githubusercontent.com/6297805/29822978-9a9a6ef0-8ccd-11e7-9ec5-934ea0f57681.jpg +:alt: Winner issue - Winner issue +Winner issue **Answer:** - .. figure:: https://user-images.githubusercontent.com/6297805/29822983-9d523402-8ccd-11e7-9fb1-5783740ee366.jpg - :alt: Winner issue answer +.. figure:: https://user-images.githubusercontent.com/6297805/29822983-9d523402-8ccd-11e7-9fb1-5783740ee366.jpg +:alt: Winner issue answer - Winner issue answer +Winner issue answer diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 077f98f0..2fbb13e0 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -31,7 +31,16 @@ class UpdateMethods(UserMethods): def on(self, event): """ - Decorator helper method around add_event_handler(). + Decorator helper method around `add_event_handler`. Example: + + >>> from telethon import TelegramClient, events + >>> client = TelegramClient(...) + >>> + >>> @client.on(events.NewMessage) + ... async def handler(event): + ... ... + ... + >>> Args: event (`_EventBuilder` | `type`): diff --git a/telethon/events/common.py b/telethon/events/common.py index 4df12836..bdbe9a62 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -82,7 +82,29 @@ class EventBuilder(abc.ABC): class EventCommon(abc.ABC): - """Intermediate class with common things to all events""" + """ + Intermediate class with common things to all events. + + Attributes: + pattern_match (`obj`): + The resulting object from calling the passed ``pattern`` function. + Here's an example using a string (defaults to regex match): + + >>> from telethon import TelegramClient, events + >>> client = TelegramClient(...) + >>> + >>> @client.on(events.NewMessage(pattern=r'hi (\w+)!')) + ... def handler(event): + ... # In this case, the result is a ``Match`` object + ... # since the ``str`` pattern was converted into + ... # the ``re.compile(pattern).match`` function. + ... print('Welcomed', event.pattern_match.group(1)) + ... + >>> + + original_update (:tl:`Update`): + The original Telegram update object that caused this event. + """ _event_name = 'Event' def __init__(self, chat_peer=None, msg_id=None, broadcast=False): @@ -146,6 +168,9 @@ class EventCommon(abc.ABC): @property def client(self): + """ + The `telethon.TelegramClient` that created this event. + """ return self._client @property diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index fffac6e5..f45f58b5 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -177,6 +177,12 @@ class Message: @property async def chat(self): + """ + The (:tl:`User` | :tl:`Chat` | :tl:`Channel`, optional) on which + the event occurred. This property may make an API call the first time + to get the most up to date version of the chat (mostly when the event + doesn't belong to a channel), so keep that in mind. + """ if self._chat is None: try: self._chat =\