diff --git a/README.rst b/README.rst index 0d6c28a3..b73c6aa6 100755 --- a/README.rst +++ b/README.rst @@ -4,16 +4,13 @@ Telethon ⭐️ Thanks **everyone** who has starred the project, it means a lot! -|logo| **Telethon** is an `asyncio -`_ **Python 3** library -to interact with Telegram's API. +|logo| **Telethon** is an asyncio_ **Python 3** +MTProto_ library to interact with Telegram_'s API. -**If you're upgrading from Telethon pre-1.0 to 1.0, please make sure to read** -`this section of the documentation -`_, -or ``pip install telethon-sync`` which is compatible with `synchronous code -`_. Don't forget to remove -the asynchronous version (``pip uninstall telethon``) if you do install sync. +.. important:: + + If you have code using Telethon before its 1.0 version, you must + read `Compatibility and Convenience`_ to learn how to migrate. What is this? ------------- @@ -27,7 +24,7 @@ heavy job for you, so you can focus on developing an application. Installing ---------- -.. code:: sh +.. code-block:: sh pip3 install telethon @@ -35,9 +32,9 @@ Installing Creating a client ----------------- -.. code:: python +.. code-block:: python - from telethon import TelegramClient, sync + from telethon import TelegramClient, events, sync # These example values won't work. You must get your own api_id and # api_hash from https://my.telegram.org, under API Development. @@ -51,7 +48,7 @@ Creating a client Doing stuff ----------- -.. code:: python +.. code-block:: python print(client.get_me().stringify()) @@ -62,14 +59,23 @@ Doing stuff messages = client.get_messages('username') messages[0].download_media() + @client.on(events.NewMessage(pattern='(?i)hi|hello')) + async def handler(event): + await event.respond('Hey!') + Next steps ---------- -Do you like how Telethon looks? Check out `Read The Docs -`_ for a more in-depth explanation, -with examples, troubleshooting issues, and more useful information. +Do you like how Telethon looks? Check out `Read The Docs`_ for a more +in-depth explanation, with examples, troubleshooting issues, and more +useful information. +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _MTProto: https://core.telegram.org/mtproto +.. _Telegram: https://telegram.org +.. _Compatibility and Convenience: https://telethon.readthedocs.io/en/latest/extra/basic/compatibility-and-convenience.html +.. _Read The Docs: https://telethon.readthedocs.io .. |logo| image:: logo.svg :width: 24pt diff --git a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst b/readthedocs/extra/advanced-usage/accessing-the-full-api.rst index 2c5ec171..dbf805ac 100644 --- a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst +++ b/readthedocs/extra/advanced-usage/accessing-the-full-api.rst @@ -29,9 +29,9 @@ can go through a sorted list of everything you can do. .. important:: All the examples in this documentation assume that you have - ``from telethon import sync`` or ``import telethon.sync`` - for the sake of simplicity and that you understand what - it does (see :ref:`asyncio-magic` for more). Simply add + ``from telethon import sync`` or ``import telethon.sync`` for the + sake of simplicity and that you understand what it does (see + :ref:`compatibility-and-convenience` for more). Simply add either line at the beginning of your project and it will work. @@ -108,10 +108,9 @@ every time its used, simply call `telethon.utils.get_input_peer`: .. note:: Since ``v0.16.2`` this is further simplified. The ``Request`` itself - 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. - + will call `client.get_input_entity + ` for you when + required, but it's good to remember what's happening. After this small parenthesis about `client.get_entity ` versus diff --git a/readthedocs/extra/advanced-usage/mastering-asyncio.rst b/readthedocs/extra/advanced-usage/mastering-asyncio.rst new file mode 100644 index 00000000..313dbf3a --- /dev/null +++ b/readthedocs/extra/advanced-usage/mastering-asyncio.rst @@ -0,0 +1,319 @@ +.. _mastering-asyncio: + +================= +Mastering asyncio +================= + +.. contents:: + + +What's asyncio? +*************** + +asyncio_ is a Python 3's built-in library. This means it's already installed if +you have Python 3. Since Python 3.5, it is convenient to work with asynchronous +code. Before (Python 3.4) we didn't have ``async`` or ``await``, but now we do. + +asyncio_ stands for *Asynchronous Input Output*. This is a very powerful +concept to use whenever you work IO. Interacting with the web or external +APIs such as Telegram's makes a lot of sense this way. + + +Why asyncio? +************ + +Asynchronous IO makes a lot of sense in a library like Telethon. +You send a request to the server (such as "get some message"), and +thanks to asyncio_, your code won't block while a response arrives. + +The alternative would be to spawn a thread for each update so that +other code can run while the response arrives. That is *a lot* more +expensive. + +The code will also run faster, because instead of switching back and +forth between the OS and your script, your script can handle it all. +Avoiding switching saves quite a bit of time, in Python or any other +language that supports asynchronous IO. It will also be cheaper, +because tasks are smaller than threads, which are smaller than processes. + + +What are asyncio basics? +************************ + +.. code-block:: python + + # First we need the asyncio library + import asyncio + + # Then we need a loop to work with + loop = asyncio.get_event_loop() + + # We also need something to run + async def main(): + for char in 'Hello, world!\n': + print(char, end='', flush=True) + await asyncio.sleep(0.2) + + # Then, we need to run the loop with a task + loop.run_until_complete(main()) + + +What does telethon.sync do? +*************************** + +The moment you import any of these: + +.. code-block:: python + + from telethon import sync, ... + # or + from telethon.sync import ... + # or + import telethon.sync + +The ``sync`` module rewrites most ``async def`` +methods in Telethon to something similar to this: + +.. code-block:: python + + def new_method(): + result = original_method() + if loop.is_running(): + # the loop is already running, return the await-able to the user + return result + else: + # the loop is not running yet, so we can run it for the user + return loop.run_until_complete(result) + + +That means you can do this: + +.. code-block:: python + + print(client.get_me().username) + +Instead of this: + +.. code-block:: python + + import asyncio + loop = asyncio.get_event_loop() + me = loop.run_until_complete(client.get_me()) + print(me.username) + + +As you can see, it's a lot of boilerplate and noise having to type +``run_until_complete`` all the time, so you can let the magic module +to rewrite it for you. But notice the comment above: it won't run +the loop if it's already running, because it can't. That means this: + +.. code-block:: python + + async def main(): + # 3. the loop is running here + print( + client.get_me() # 4. this will return a coroutine! + .username # 5. this fails, coroutines don't have usernames + ) + + loop.run_until_complete( # 2. run the loop and the ``main()`` coroutine + main() # 1. calling ``async def`` "returns" a coroutine + ) + + +Will fail. So if you're inside an ``async def``, then the loop is +running, and if the loop is running, you must ``await`` things yourself: + +.. code-block:: python + + async def main(): + print((await client.get_me()).username) + + loop.run_until_complete(main()) + + +What are async, await and coroutines? +************************************* + +The ``async`` keyword lets you define asynchronous functions, +also known as coroutines, and also iterate over asynchronous +loops or use ``async with``: + +.. code-block:: python + + import asyncio + + async def main(): + # ^ this declares the main() coroutine function + + async with client: + # ^ this is an asynchronous with block + + async for message in client.iter_messages(chat): + # ^ this is a for loop over an asynchronous generator + + print(message.sender.username) + + loop = asyncio.get_event_loop() + # ^ this assigns the default event loop from the main thread to a variable + + loop.run_until_complete(main()) + # ^ this runs the *entire* loop until the main() function finishes. + # While the main() function does not finish, the loop will be running. + # While the loop is running, you can't run it again. + + +The ``await`` keyword blocks the *current* task, and the loop can run +other tasks. Tasks can be thought of as "threads", since many can run +concurrently: + +.. code-block:: python + + import asyncio + + async def hello(delay): + await asyncio.sleep(delay) # await tells the loop this task is "busy" + print('hello') # eventually the loop resumes the code here + + async def world(delay): + # the loop decides this method should run first + await asyncio.sleep(delay) # await tells the loop this task is "busy" + print('world') # eventually the loop finishes all tasks + + loop = asyncio.get_event_loop() # get the default loop for the main thread + loop.create_task(world(2)) # create the world task, passing 2 as delay + loop.create_task(hello(delay=1)) # another task, but with delay 1 + try: + # run the event loop forever; ctrl+c to stop it + # we could also run the loop for three seconds: + # loop.run_until_complete(asyncio.sleep(3)) + loop.run_forever() + except KeyboardInterrupt: + pass + +The same example, but without the comment noise: + +.. code-block:: python + + import asyncio + + async def hello(delay): + await asyncio.sleep(delay) + print('hello') + + async def world(delay): + await asyncio.sleep(delay) + print('world') + + loop = asyncio.get_event_loop() + loop.create_task(world(2)) + loop.create_task(hello(1)) + loop.run_until_complete(asyncio.sleep(3)) + + +Can I use threads? +****************** + +Yes, you can, but you must understand that the loops themselves are +not thread safe. and you must be sure to know what is happening. You +may want to create a loop in a new thread and make sure to pass it to +the client: + +.. code-block:: python + + import asyncio + import threading + + def go(): + loop = asyncio.new_event_loop() + client = TelegramClient(..., loop=loop) + ... + + threading.Thread(target=go).start() + + +Generally, **you don't need threads** unless you know what you're doing. +Just create another task, as shown above. If you're using the Telethon +with a library that uses threads, you must be careful to use ``threading.Lock`` +whenever you use the client, or enable the compatible mode. For that, see +:ref:`compatibility-and-convenience`. + +You may have seen this error: + +.. code-block:: text + + RuntimeError: There is no current event loop in thread 'Thread-1'. + +It just means you didn't create a loop for that thread, and if you don't +pass a loop when creating the client, it uses ``asyncio.get_event_loop()``, +which only works in the main thread. + +What else can asyncio do? +************************* + +Asynchronous IO is a really powerful tool, as we've seen. There are plenty +of other useful libraries that also use asyncio_ and that you can integrate +with Telethon. + +* `aiocron `_ lets you schedule + things to run things at a desired time, or run some tasks daily. +* `aiohttp `_ is like the infamous + `requests `_ but asynchronous. +* `quart `_ is an asynchronous alternative + to `Flask `_. + +And of course, `asyncio `_ +itself! It has a lot of methods that let you do nice things. For example, +you can run requests in parallel: + +.. code-block:: python + + async def main(): + last, sent, download_path = await asyncio.gather( + client.get_messages('TelethonChat', 10), + client.send_message('TelethonOfftopic', 'Hey guys!'), + client.download_profile_photo('TelethonChat') + ) + + loop.run_until_complete(main()) + + +This code will get the 10 last messages from `@TelethonChat +`_, send one to `@TelethonOfftopic +`_, and also download the profile +photo of the main group. asyncio_ will run all these three tasks +at the same time. You can run all the tasks you want this way. + +A different way would be: + +.. code-block:: python + + loop.create_task(client.get_messages('TelethonChat', 10)) + loop.create_task(client.send_message('TelethonOfftopic', 'Hey guys!')) + loop.create_task(client.download_profile_photo('TelethonChat')) + +They will run in the background as long as the loop is running too. + + +Why does client.start() work outside async? +******************************************* + +Because it's so common that it's really convenient to offer said +functionality by default. This means you can set up all your event +handlers and start the client without worrying about loops at all. + +Using the client in a ``with`` block, `start +`, `run_until_disconnected +`, and +`disconnect ` +all support this. + +Where can I read more? +********************** + +`Check out my blog post +`_ about asyncio_, which +has some more examples and pictures to help you understand what happens +when the loop runs. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html diff --git a/readthedocs/extra/advanced-usage/sessions.rst b/readthedocs/extra/advanced-usage/sessions.rst index 1b23acd7..eeae7940 100644 --- a/readthedocs/extra/advanced-usage/sessions.rst +++ b/readthedocs/extra/advanced-usage/sessions.rst @@ -4,6 +4,11 @@ Session Files ============== +.. contents:: + +What are sessions? +****************** + 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, diff --git a/readthedocs/extra/advanced-usage/update-modes.rst b/readthedocs/extra/advanced-usage/update-modes.rst index c3391499..d8950834 100644 --- a/readthedocs/extra/advanced-usage/update-modes.rst +++ b/readthedocs/extra/advanced-usage/update-modes.rst @@ -27,7 +27,6 @@ Behind the scenes, this method is ``await``'ing on the `client.disconnected ` property, so the code above and the following are equivalent: - .. code-block:: python import asyncio @@ -63,3 +62,12 @@ also be ran while the loop is running, so you can do this: await client.run_until_disconnected() loop.run_until_complete(main()) + + +If you need to process updates sequentially (i.e. not in parallel), +you should set ``sequential_updates=True`` when creating the client: + +.. code-block:: python + + with TelegramClient(..., sequential_updates=True) as client: + ... diff --git a/readthedocs/extra/basic/asyncio-magic.rst b/readthedocs/extra/basic/asyncio-magic.rst deleted file mode 100644 index 4cba2c28..00000000 --- a/readthedocs/extra/basic/asyncio-magic.rst +++ /dev/null @@ -1,322 +0,0 @@ -.. _asyncio-magic: - -================== -Magic with asyncio -================== - -.. important:: - - TL; DR; If you've upgraded to Telethon 1.0 from a previous version - **and you're not using events or updates**, add this line: - - .. code-block:: python - - import telethon.sync - - At the beginning of your main script and you will be good. If you **do** - use updates or events, keep reading, or ``pip install telethon-sync``, a - branch that mimics the ``asyncio`` code with threads and should work - under Python 3.4. - - You might also want to check the :ref:`changelog`. - - -The sync module -*************** - -It's time to tell you the truth. The library has been doing magic behind -the scenes. We're sorry to tell you this, but at least it wasn't dark magic! - -You may have noticed one of these lines across the documentation: - -.. code-block:: python - - from telethon import sync - # or - import telethon.sync - -Either of these lines will import the *magic* ``sync`` module. When you -import this module, you can suddenly use all the methods defined in the -:ref:`TelegramClient ` like so: - -.. code-block:: python - - client.send_message('me', 'Hello!') - - for dialog in client.iter_dialogs(): - print(dialog.title) - - -What happened behind the scenes is that all those methods, called *coroutines*, -were rewritten to be normal methods that will block (with some exceptions). -This means you can use the library without worrying about ``asyncio`` and -event loops. - -However, this only works until you run the event loop yourself explicitly: - -.. code-block:: python - - import asyncio - - async def coro(): - client.send_message('me', 'Hello!') # <- no longer works! - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - - -What things will work and when? -******************************* - -You can use all the methods in the :ref:`TelegramClient ` -in a synchronous, blocking way without trouble, as long as you're not running -the loop as we saw above (the ``loop.run_until_complete(...)`` line runs "the -loop"). If you're running the loop, then *you* are the one responsible to -``await`` everything. So to fix the code above: - -.. code-block:: python - - import asyncio - - async def coro(): - await client.send_message('me', 'Hello!') - # ^ notice this new await - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - -The library can only run the loop until the method completes if the loop -isn't already running, which is why the magic can't work if you run the -loop yourself. - -**When you work with updates or events**, the loop needs to be -running one way or another (using `client.run_until_disconnected() -` runs the loop), -so your event handlers must be ``async def``. - -.. important:: - - Turning your event handlers into ``async def`` is the biggest change - between Telethon pre-1.0 and 1.0, but updating will likely cause a - noticeable speed-up in your programs. Keep reading! - - -So in short, you can use **all** methods in the client with ``await`` or -without it if the loop isn't running: - -.. code-block:: python - - client.send_message('me', 'Hello!') # works - - async def main(): - await client.send_message('me', 'Hello!') # also works - - loop.run_until_complete(main()) - - -When you work with updates, you should stick using the ``async def main`` -way, since your event handlers will be ``async def`` too. - -.. note:: - - There are two exceptions. Both `client.run_until_disconnected() - ` and - `client.start() ` work in - and outside of ``async def`` for convenience without importing the - magic module. The rest of methods remain ``async`` unless you import it. - -You can skip the rest if you already know how ``asyncio`` works and you -already understand what the magic does and how it works. Just remember -to ``await`` all your methods if you're inside an ``async def`` or are -using updates and you will be good. - - -Why asyncio? -************ - -Python's `asyncio `_ is the -standard way to run asynchronous code from within Python. Since Python 3.5, -using ``async def`` and ``await`` became possible, and Python 3.6 further -improves what you can do with asynchronous code, although it's not the only -way (other projects like `Trio `_ also exist). - -Telegram is a service where all API calls are executed in an asynchronous -way. You send your request, and eventually, Telegram will process it and -respond to it. It feels natural to make a library that also behaves this -way: you send a request, and you can ``await`` for its result. - -Now that we know that Telegram's API follows an asynchronous model, you -should understand the benefits of developing a library that does the same, -it greatly simplifies the internal code and eases working with the API. - -Using ``asyncio`` keeps a cleaner library that will be easier to understand, -develop, and that will be faster than using threads, which are harder to get -right and can cause issues. It also enables to use the powerful ``asyncio`` -system such as futures, timeouts, cancellation, etc. in a natural way. - -If you're still not convinced or you're just not ready for using ``asyncio``, -the library offers a synchronous interface without the need for all the -``async`` and ``await`` you would otherwise see. `Follow this link -`_ to find out more. - - -How do I get started? -********************* - -To get started with ``asyncio``, all you need is to setup your main -``async def`` like so: - -.. code-block:: python - - import asyncio - - async def main(): - pass # Your code goes here - - if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - -You don't need to ``import telethon.sync`` if you're going to work this -way. This is the best way to work in real programs since the loop won't -be starting and ending all the time, but is a bit more annoying to setup. - -Inside ``async def main()``, you can use the ``await`` keyword. Most -methods in the :ref:`TelegramClient ` are ``async def``. -You must ``await`` all ``async def``, also known as a *coroutines*: - -.. code-block:: python - - async def main(): - client = TelegramClient(...) - - # client.start() is a coroutine (async def), it needs an await - await client.start() - - # Sending a message also interacts with the API, and needs an await - await client.send_message('me', 'Hello myself!') - - -If you don't know anything else about ``asyncio``, this will be enough -to get you started. Once you're ready to learn more about it, you will -be able to use that power and everything you've learnt with Telethon. -Just remember that if you use ``await``, you need to be inside of an -``async def``. - -Another way to use ``async def`` is to use ``loop.run_until_complete(f())``, -but the loop must not be running before. - -If you want to handle updates (and don't let the script die), you must -`await client.run_until_disconnected() -` -which is a property that you can wait on until you call -`await client.disconnect() -`: - - -.. code-block:: python - - client = TelegramClient(...) - - @client.on(events.NewMessage) - async def handler(event): - print(event) - - async def main(): - await client.start() - await client.run_until_disconnected() - - if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - -`client.run_until_disconnected() -` and -`client.start() -` are special-cased and work -inside or outside ``async def`` for convenience, even without importing -the ``sync`` module, so you can also do this: - -.. code-block:: python - - client = TelegramClient(...) - - @client.on(events.NewMessage) - async def handler(event): - print(event) - - if __name__ == '__main__': - client.start() - client.run_until_disconnected() - - -Which methods should I use and when? -************************************ - -Something to note is that you must always get an event loop if you -want to be able to make any API calls. This is done as follows: - -.. code-block:: python - - import asyncio - loop = asyncio.get_event_loop() - -The loop must be running, or things will never get sent. -Normally, you use ``run_until_complete``: - -.. code-block:: python - - async def coroutine(): - await asyncio.sleep(1) - - loop.run_until_complete(coroutine()) - -Note that ``asyncio.sleep`` is in itself a coroutine, so this will -work too: - -.. code-block:: python - - loop.run_until_complete(asyncio.sleep(1)) - -Generally, you make an ``async def main()`` if you need to ``await`` -a lot of things, instead of typing ``run_until_complete`` all the time: - -.. code-block:: python - - async def main(): - message = await client.send_message('me', 'Hi') - await asyncio.sleep(1) - await message.delete() - - loop.run_until_complete(main()) - - # vs - - message = loop.run_until_complete(client.send_message('me', 'Hi')) - loop.run_until_complete(asyncio.sleep(1)) - loop.run_until_complete(message.delete()) - -You can see that the first version has more lines, but you had to type -a lot less. You can also rename the run method to something shorter: - -.. code-block:: python - - # Note no parenthesis (), we're not running it, just copying the method - rc = loop.run_until_complete - message = rc(client.send_message('me', 'Hi')) - rc(asyncio.sleep(1)) - rc(message.delete()) - -The documentation generally runs the loop until complete behind the -scenes if you've imported the magic ``sync`` module, but if you haven't, -you need to run the loop yourself. We recommend that you use the -``async def main()`` method to do all your work with ``await``. -It's the easiest and most performant thing to do. - - -More resources to learn asyncio -******************************* - -If you would like to learn a bit more about why ``asyncio`` is something -you should learn, `check out my blog post -`_ that goes into more detail. diff --git a/readthedocs/extra/basic/compatibility-and-convenience.rst b/readthedocs/extra/basic/compatibility-and-convenience.rst new file mode 100644 index 00000000..2111d39a --- /dev/null +++ b/readthedocs/extra/basic/compatibility-and-convenience.rst @@ -0,0 +1,171 @@ +.. _compatibility-and-convenience: + +============================= +Compatibility and Convenience +============================= + +Telethon is an ``asyncio`` library. Compatibility is an important concern, +and while it can't always be kept and mistakes happens, the :ref:`changelog` +is there to tell you when these important changes happen. + +.. contents:: + + +Compatibility +************* + +.. important:: + + **You should not enable the thread-compatibility mode for new projects.** + It comes with a cost, and new projects will greatly benefit from using + ``asyncio`` by default such as increased speed and easier reasoning about + the code flow. You should only enable it for old projects you don't have + the time to upgrade to ``asyncio``. + +There exists a fair amount of code online using Telethon before it reached +its 1.0 version, where it became fully asynchronous by default. Since it was +necessary to clean some things, compatibility was not kept 100% but the +changes are simple: + +.. code-block:: python + + # 1. The library no longer uses threads. + # Add this at the **beginning** of your script to work around that. + from telethon import full_sync + full_sync.enable() + + # 2. client.connect() no longer returns True. + # Change this... + assert client.connect() + # ...for this: + client.connect() + + # 3. client.idle() no longer exists. + # Change this... + client.idle() + # ...to this: + client.run_until_disconnected() + + # 4. client.add_update_handler no longer exists. + # Change this... + client.add_update_handler(handler) + # ...to this: + client.add_event_handler(handler) + + # 5. It's good practice to stop the full_sync mode once you're done + try: + ... # all your code in here + finally: + full_sync.stop() + + +Convenience +*********** + +.. note:: + + The entire documentation assumes you have done one of the following: + + .. code-block:: python + + from telethon import TelegramClient, sync + # or + from telethon.sync import TelegramClient + + This makes the examples shorter and easier to think about. + +For quick scripts that don't need updates, it's a lot more convenient to +forget about ``full_sync`` or ``asyncio`` and just work with sequential code. +This can prove to be a powerful hybrid for running under the Python REPL too. + +.. code-block:: python + + from telethon.sync import TelegramClient + # ^~~~~ note this part; it will manage the asyncio loop for you + + with TelegramClient(...) as client: + print(client.get_me().username) + # ^ notice the lack of await, or loop.run_until_complete(). + # Since there is no loop running, this is done behind the scenes. + # + message = client.send_message('me', 'Hi!') + import time + time.sleep(5) + message.delete() + + # You can also have an hybrid between a synchronous + # part and asynchronous event handlers. + # + from telethon import events + @client.on(events.NewMessage(pattern='(?i)hi|hello')) + async def handler(event): + await event.reply('hey') + + client.run_until_disconnected() + + +Some methods, such as ``with``, ``start``, ``disconnect`` and +``run_until_disconnected`` work both in synchronous and asynchronous +contexts by default for convenience, and to avoid the little overhead +it has when using methods like sending a message, getting messages, etc. +This keeps the best of both worlds as a sane default. + +.. note:: + + As a rule of thumb, if you're inside an ``async def`` and you need + the client, you need to ``await`` calls to the API. If you call other + functions that also need API calls, make them ``async def`` and ``await`` + them too. Otherwise, there is no need to do so with this mode. + +Speed +***** + +When you're ready to micro-optimize your application, or if you simply +don't need to call any non-basic methods from a synchronous context, +just get rid of both ``telethon.sync`` and ``telethon.full_sync``: + +.. code-block:: python + + import asyncio + from telethon import TelegramClient, events + + async def main(): + async with TelegramClient(...) as client: + print((await client.get_me()).username) + # ^_____________________^ notice these parenthesis + # You want to ``await`` the call, not the username. + # + message = await client.send_message('me', 'Hi!') + await asyncio.sleep(5) + await message.delete() + + @client.on(events.NewMessage(pattern='(?i)hi|hello')) + async def handler(event): + await event.reply('hey') + + await client.run_until_disconnected() + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + + +The ``telethon.sync`` magic module simply wraps every method behind: + +.. code-block:: python + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +So that you don't have to write it yourself every time. That's the +overhead you pay if you import it, and what you save if you don't. + +Learning +******** + +You know the library uses ``asyncio`` everywhere, and you want to learn +how to do things right. Even though ``asyncio`` is its own topic, the +documentation wants you to learn how to use Telethon correctly, and for +that, you need to use ``asyncio`` correctly too. For this reason, there +is a section called :ref:`mastering-asyncio` that will introduce you to +the ``asyncio`` world, with links to more resources for learning how to +use it. Feel free to check that section out once you have read the rest. diff --git a/readthedocs/extra/basic/entities.rst b/readthedocs/extra/basic/entities.rst index 897fe997..5b5f6453 100644 --- a/readthedocs/extra/basic/entities.rst +++ b/readthedocs/extra/basic/entities.rst @@ -5,6 +5,40 @@ Users, Chats and Channels ========================= +.. important:: + + TL;DR; If you're here because of *"Could not find the input entity for"*, + you must ask yourself "how did I find this entity through official + applications"? Now do the same with the library. Use what applies: + + .. code-block:: python + + with client: + # Does it have an username? Use it! + entity = client.get_entity(username) + + # Do you have a conversation open with them? Get dialogs. + client.get_dialogs() + + # Are they participant of some group? Get them. + client.get_participants('TelethonChat') + + # Is the entity the original sender of a forwarded message? Get it. + client.get_messages('TelethonChat', 100) + + # NOW you can use the ID, anywhere! + entity = client.get_entity(123456) + client.send_message(123456, 'Hi!') + + Once the library has "seen" the entity, you can use their **integer** ID. + You can't use entities from IDs the library hasn't seen. You must make the + library see them *at least once* and disconnect properly. You know where + the entities are and you must tell the library. It won't guess for you. + + +.. contents:: + + Introduction ************ diff --git a/readthedocs/extra/basic/getting-started.rst b/readthedocs/extra/basic/getting-started.rst index ddfa9fbe..b54a9e46 100644 --- a/readthedocs/extra/basic/getting-started.rst +++ b/readthedocs/extra/basic/getting-started.rst @@ -5,6 +5,8 @@ Getting Started =============== +.. contents:: + Simple Installation ******************* diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst index 983472b5..fa35a9ed 100644 --- a/readthedocs/extra/basic/installation.rst +++ b/readthedocs/extra/basic/installation.rst @@ -4,6 +4,8 @@ Installation ============ +.. contents:: + Automatic Installation ********************** diff --git a/readthedocs/extra/basic/telegram-client.rst b/readthedocs/extra/basic/telegram-client.rst index 09db60c1..fd059fd7 100644 --- a/readthedocs/extra/basic/telegram-client.rst +++ b/readthedocs/extra/basic/telegram-client.rst @@ -4,10 +4,6 @@ TelegramClient ============== - -Introduction -************ - .. note:: Make sure to use the friendly methods described in :ref:`telethon-client`! diff --git a/readthedocs/extra/basic/working-with-updates.rst b/readthedocs/extra/basic/working-with-updates.rst index aaca6b59..944317d3 100644 --- a/readthedocs/extra/basic/working-with-updates.rst +++ b/readthedocs/extra/basic/working-with-updates.rst @@ -6,14 +6,9 @@ Working with Updates .. important:: - Make sure you have read at least the first part of :ref:`asyncio-magic` - before working with updates. **This is a big change from Telethon pre-1.0 - and 1.0, and your old handlers won't work with this version**. - - To port your code to the new version, you should just prefix all your - event handlers with ``async`` and ``await`` everything that makes an - API call, such as replying, deleting messages, etc. - + Coming from Telethon before it reached its version 1.0? + Make sure to read :ref:`compatibility-and-convenience`! + Otherwise, you can ignore this note and just follow along. 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 diff --git a/readthedocs/extra/changelog.rst b/readthedocs/extra/changelog.rst index 6f70bfa7..0d5f42d4 100644 --- a/readthedocs/extra/changelog.rst +++ b/readthedocs/extra/changelog.rst @@ -453,11 +453,8 @@ Synchronous magic (v1.0) .. important:: If you come from Telethon pre-1.0 you **really** want to read - :ref:`asyncio-magic` to port your scripts to the new version. - - If you're not ready for this, you can ``pip install telethon-sync``. - It's a synchronous branch that mimics the ``asyncio`` version with - threads and should work under Python 3.4 + :ref:`compatibility-and-convenience` to port your scripts to + the new version. The library has been around for well over a year. A lot of improvements have been made, a lot of user complaints have been fixed, and a lot of user desires diff --git a/readthedocs/extra/examples/bots.rst b/readthedocs/extra/examples/bots.rst index 20bb26d3..a3343c1b 100644 --- a/readthedocs/extra/examples/bots.rst +++ b/readthedocs/extra/examples/bots.rst @@ -7,6 +7,8 @@ Bots These examples assume you have read :ref:`accessing-the-full-api`. +.. contents:: + Talking to Inline Bots ********************** diff --git a/readthedocs/extra/examples/chats-and-channels.rst b/readthedocs/extra/examples/chats-and-channels.rst index 209be5d7..12844c87 100644 --- a/readthedocs/extra/examples/chats-and-channels.rst +++ b/readthedocs/extra/examples/chats-and-channels.rst @@ -7,6 +7,8 @@ Working with Chats and Channels These examples assume you have read :ref:`accessing-the-full-api`. +.. contents:: + Joining a chat or channel ************************* diff --git a/readthedocs/extra/examples/telegram-client.rst b/readthedocs/extra/examples/telegram-client.rst index c6ea8aa0..41fa2ca7 100644 --- a/readthedocs/extra/examples/telegram-client.rst +++ b/readthedocs/extra/examples/telegram-client.rst @@ -60,6 +60,7 @@ the ``telethon.sync`` package and that you have a client ready to use. .. contents:: + Authorization ************* diff --git a/readthedocs/extra/examples/users.rst b/readthedocs/extra/examples/users.rst index cc5fbe19..54cae92c 100644 --- a/readthedocs/extra/examples/users.rst +++ b/readthedocs/extra/examples/users.rst @@ -7,6 +7,8 @@ Users These examples assume you have read :ref:`accessing-the-full-api`. +.. contents:: + Retrieving full information *************************** diff --git a/readthedocs/extra/examples/working-with-messages.rst b/readthedocs/extra/examples/working-with-messages.rst index 4f741425..d574a6bb 100644 --- a/readthedocs/extra/examples/working-with-messages.rst +++ b/readthedocs/extra/examples/working-with-messages.rst @@ -7,6 +7,8 @@ Working with messages These examples assume you have read :ref:`accessing-the-full-api`. +.. contents:: + Forwarding messages ******************* diff --git a/readthedocs/index.rst b/readthedocs/index.rst index d2b0b62a..110f4da7 100644 --- a/readthedocs/index.rst +++ b/readthedocs/index.rst @@ -15,25 +15,10 @@ or use the menu on the left. Remember to read the :ref:`changelog` when you upgrade! .. important:: - If you're new here, you want to read :ref:`getting-started`. If you're - looking for the method reference, you should check :ref:`telethon-client`. - The mentioned :ref:`telethon-client` is an important section and it - contains the friendly methods that **you should use** most of the time. - - -.. note:: - The library uses `asyncio `_ - under the hood, but you don't need to know anything about it unless you're - going to work with updates! If you're a user of Telethon pre-1.0 and you - aren't ready to convert your event handlers into ``async``, you can use - `a simpler version `_ - (select the "sync" version in ``readthedocs``' bottom left corner). - - If you used Telethon pre-1.0 but your scripts don't use updates or threads, - running ``import telethon.sync`` should make them Just Work. Otherwise, - we have :ref:`asyncio-magic` to teach you why ``asyncio`` is good and - how to use it. + * Are you new here? Jump straight into :ref:`getting-started`! + * Looking for available friendly methods? See :ref:`telethon-client`. + * Used Telethon before v1.0? See :ref:`compatibility-and-convenience`. What is this? @@ -56,8 +41,8 @@ heavy job for you, so you can focus on developing an application. extra/basic/creating-a-client extra/basic/telegram-client extra/basic/entities - extra/basic/asyncio-magic extra/basic/working-with-updates + extra/basic/compatibility-and-convenience .. _Advanced-usage: @@ -70,6 +55,7 @@ heavy job for you, so you can focus on developing an application. extra/advanced-usage/sessions extra/advanced-usage/update-modes extra/advanced-usage/mastering-telethon + extra/advanced-usage/mastering-asyncio .. _Examples: diff --git a/readthedocs/telethon.extensions.rst b/readthedocs/telethon.extensions.rst index 2b834277..83bb4d93 100644 --- a/readthedocs/telethon.extensions.rst +++ b/readthedocs/telethon.extensions.rst @@ -25,11 +25,3 @@ telethon\.extensions\.html module :members: :undoc-members: :show-inheritance: - -telethon\.extensions\.tcpclient module --------------------------------------- - -.. automodule:: telethon.extensions.tcpclient - :members: - :undoc-members: - :show-inheritance: diff --git a/telethon/client/messages.py b/telethon/client/messages.py index d9cb3d92..c86acfdc 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -28,7 +28,6 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods): reverse=False, _total=None): """ Iterator over the message history for the specified entity. - If either `search`, `filter` or `from_user` are provided, :tl:`messages.Search` will be used instead of :tl:`messages.getHistory`. @@ -48,6 +47,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods): Number of messages to be retrieved. Due to limitations with the API retrieving more than 3000 messages will take longer than half a minute (or even more based on previous calls). + The limit may also be ``None``, which would eventually return the whole history. @@ -411,8 +411,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods): Whether the existing draft should be cleared or not. Has no effect when sending a file. - buttons (`list`, `custom.Button `, - :tl:`KeyboardButton`): + buttons (`list`, `custom.Button `, :tl:`KeyboardButton`): The matrix (list of lists), row list or button to be shown after sending the message. This parameter will only work if you have signed in as a bot. You can also pass your own @@ -616,8 +615,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods): The file object that should replace the existing media in the message. - buttons (`list`, `custom.Button `, - :tl:`KeyboardButton`): + buttons (`list`, `custom.Button `, :tl:`KeyboardButton`): The matrix (list of lists), row list or button to be shown after sending the message. This parameter will only work if you have signed in as a bot. You can also pass your own diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 42707ff8..a95f869a 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -71,6 +71,10 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods): Optional JPEG thumbnail (for documents). **Telegram will ignore this parameter** unless you pass a ``.jpg`` file! + The file must also be small in dimensions and in-disk size. + Successful thumbnails were files below 20kb and 200x200px. + Width/height and dimensions/size ratios may be important. + allow_cache (`bool`, optional): Whether to allow using the cached version stored in the database or not. Defaults to ``True`` to avoid re-uploads. @@ -94,8 +98,7 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods): Set `allow_cache` to ``False`` if you sent the same file without this setting before for it to work. - buttons (`list`, `custom.Button `, - :tl:`KeyboardButton`): + buttons (`list`, `custom.Button `, :tl:`KeyboardButton`): The matrix (list of lists), row list or button to be shown after sending the message. This parameter will only work if you have signed in as a bot. You can also pass your own diff --git a/telethon/client/users.py b/telethon/client/users.py index b59f7ae7..cc92529a 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -304,7 +304,7 @@ class UserMethods(TelegramBaseClient): return utils.get_input_peer(peer) raise ValueError( - 'Could not find the input entity for "{}". Please read https://' + 'Could not find the input entity for {!r}. Please read https://' 'telethon.readthedocs.io/en/latest/extra/basic/entities.html to' ' find out more details.' .format(peer) diff --git a/telethon/tl/custom/inline.py b/telethon/tl/custom/inline.py index 1983056e..5e576c2d 100644 --- a/telethon/tl/custom/inline.py +++ b/telethon/tl/custom/inline.py @@ -34,8 +34,7 @@ class InlineBuilder: game (`bool`, optional): May be ``True`` to indicate that the game will be sent. - buttons (`list`, `custom.Button `, - :tl:`KeyboardButton`, optional): + buttons (`list`, `custom.Button `, :tl:`KeyboardButton`, optional): Same as ``buttons`` for `client.send_message `. diff --git a/telethon/utils.py b/telethon/utils.py index d6e4b37d..18b82ca7 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -33,6 +33,9 @@ mimetypes.add_type('audio/ogg', '.ogg') USERNAME_RE = re.compile( r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?' ) +TG_JOIN_RE = re.compile( + r'tg://(join)?invite=' +) # The only shorter-than-five-characters usernames are those used for some # special, very well known bots. This list may be incomplete though: @@ -646,15 +649,16 @@ def parse_phone(phone): def parse_username(username): - """Parses the given username or channel access hash, given - a string, username or URL. Returns a tuple consisting of - both the stripped, lowercase username and whether it is - a joinchat/ hash (in which case is not lowercase'd). + """ + Parses the given username or channel access hash, given + a string, username or URL. Returns a tuple consisting of + both the stripped, lowercase username and whether it is + a joinchat/ hash (in which case is not lowercase'd). - Returns ``None`` if the ``username`` is not valid. + Returns ``(None, False)`` if the ``username`` or link is not valid. """ username = username.strip() - m = USERNAME_RE.match(username) + m = USERNAME_RE.match(username) or TG_JOIN_RE.match(username) if m: username = username[m.end():] is_invite = bool(m.group(1)) diff --git a/telethon_examples/assistant.py b/telethon_examples/assistant.py index 38ed0e82..6775fd7d 100644 --- a/telethon_examples/assistant.py +++ b/telethon_examples/assistant.py @@ -108,6 +108,7 @@ LEARN_PYTHON = ( "• [Official Docs](https://docs.python.org/3/tutorial/index.html).\n" "• [Dive Into Python 3](http://www.diveintopython3.net/).\n" "• [Learn Python](https://www.learnpython.org/).\n" + "• [Project Python](http://projectpython.net/).\n" "• [Computer Science Circles](https://cscircles.cemc.uwaterloo.ca/).\n" "• [MIT OpenCourse](https://ocw.mit.edu/courses/electrical-engineering-" "and-computer-science/6-0001-introduction-to-computer-science-and-progr"