mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-11-04 01:47:27 +03:00 
			
		
		
		
	Merge branch 'master' into v2
This commit is contained in:
		
						commit
						b3f0c3d2ea
					
				| 
						 | 
				
			
			@ -85,7 +85,7 @@ manually.
 | 
			
		|||
 | 
			
		||||
    Thanks to `@bb010g`_ for writing down this nice list.
 | 
			
		||||
 | 
			
		||||
.. _cryptg: https://github.com/Lonami/cryptg
 | 
			
		||||
.. _cryptg: https://github.com/cher-nov/cryptg
 | 
			
		||||
.. _pyaes: https://github.com/ricmoo/pyaes
 | 
			
		||||
.. _pillow: https://python-pillow.org
 | 
			
		||||
.. _aiohttp: https://docs.aiohttp.org
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -140,3 +140,54 @@ where the following keys are allowed:
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
.. __: https://github.com/nibrag/aiosocks
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Using MTProto Proxies
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
MTProto Proxies are Telegram's alternative to normal proxies,
 | 
			
		||||
and work a bit differently. The following protocols are available:
 | 
			
		||||
 | 
			
		||||
* ``ConnectionTcpMTProxyAbridged``
 | 
			
		||||
* ``ConnectionTcpMTProxyIntermediate``
 | 
			
		||||
* ``ConnectionTcpMTProxyRandomizedIntermediate`` (preferred)
 | 
			
		||||
 | 
			
		||||
For now, you need to manually specify these special connection modes
 | 
			
		||||
if you want to use a MTProto Proxy. Your code would look like this:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    from telethon import TelegramClient, connection
 | 
			
		||||
    #   we need to change the connection ^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
    client = TelegramClient(
 | 
			
		||||
        'anon',
 | 
			
		||||
        api_id,
 | 
			
		||||
        api_hash,
 | 
			
		||||
 | 
			
		||||
        # Use one of the available connection modes.
 | 
			
		||||
        # Normally, this one works with most proxies.
 | 
			
		||||
        connection=connection.ConnectionTcpMTProxyRandomizedIntermediate,
 | 
			
		||||
 | 
			
		||||
        # Then, pass the proxy details as a tuple:
 | 
			
		||||
        #     (host name, port, proxy secret)
 | 
			
		||||
        #
 | 
			
		||||
        # If the proxy has no secret, the secret must be:
 | 
			
		||||
        #     '00000000000000000000000000000000'
 | 
			
		||||
        proxy=('mtproxy.example.com', 2002, 'secret')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
In future updates, we may make it easier to use MTProto Proxies
 | 
			
		||||
(such as avoiding the need to manually pass ``connection=``).
 | 
			
		||||
 | 
			
		||||
In short, the same code above but without comments to make it clearer:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    from telethon import TelegramClient, connection
 | 
			
		||||
 | 
			
		||||
    client = TelegramClient(
 | 
			
		||||
        'anon', api_id, api_hash,
 | 
			
		||||
        connection=connection.ConnectionTcpMTProxyRandomizedIntermediate,
 | 
			
		||||
        proxy=('mtproxy.example.com', 2002, 'secret')
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,7 +79,7 @@ with a ``'hi!'`` message.
 | 
			
		|||
.. note::
 | 
			
		||||
 | 
			
		||||
    Event handlers **must** be ``async def``. After all,
 | 
			
		||||
    Telethon is an asynchronous library based on asyncio_,
 | 
			
		||||
    Telethon is an asynchronous library based on `asyncio`,
 | 
			
		||||
    which is a safer and often faster approach to threads.
 | 
			
		||||
 | 
			
		||||
    You **must** ``await`` all method calls that use
 | 
			
		||||
| 
						 | 
				
			
			@ -157,5 +157,3 @@ Make sure you understand the code seen here before continuing!
 | 
			
		|||
As a rule of thumb, remember that new message events behave just
 | 
			
		||||
like message objects, so you can do with them everything you can
 | 
			
		||||
do with a message object.
 | 
			
		||||
 | 
			
		||||
.. _asyncio: https://docs.python.org/3/library/asyncio.html
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,11 +10,11 @@ Mastering asyncio
 | 
			
		|||
What's asyncio?
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
asyncio_ is a Python 3's built-in library. This means it's already installed if
 | 
			
		||||
`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
 | 
			
		||||
`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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ 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.
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +234,7 @@ the client:
 | 
			
		|||
 | 
			
		||||
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``
 | 
			
		||||
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`.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -254,7 +254,7 @@ client.run_until_disconnected() blocks!
 | 
			
		|||
 | 
			
		||||
All of what `client.run_until_disconnected()
 | 
			
		||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` does is
 | 
			
		||||
run the asyncio_'s event loop until the client is disconnected. That means
 | 
			
		||||
run the `asyncio`'s event loop until the client is disconnected. That means
 | 
			
		||||
*the loop is running*. And if the loop is running, it will run all the tasks
 | 
			
		||||
in it. So if you want to run *other* code, create tasks for it:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -274,9 +274,10 @@ in it. So if you want to run *other* code, create tasks for it:
 | 
			
		|||
This creates a task for a clock that prints the time every second.
 | 
			
		||||
You don't need to use `client.run_until_disconnected()
 | 
			
		||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` either!
 | 
			
		||||
You just need to make the loop is running, somehow. ``asyncio.run_forever``
 | 
			
		||||
and ``asyncio.run_until_complete`` can also be used to run the loop, and
 | 
			
		||||
Telethon will be happy with any approach.
 | 
			
		||||
You just need to make the loop is running, somehow. `loop.run_forever()
 | 
			
		||||
<asyncio.loop.run_forever()>` and `loop.run_until_complete()
 | 
			
		||||
<asyncio.loop.run_until_complete>` can also be used to run
 | 
			
		||||
the loop, and Telethon will be happy with any approach.
 | 
			
		||||
 | 
			
		||||
Of course, there are better tools to run code hourly or daily, see below.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +286,7 @@ 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
 | 
			
		||||
of other useful libraries that also use `asyncio` and that you can integrate
 | 
			
		||||
with Telethon.
 | 
			
		||||
 | 
			
		||||
* `aiohttp <https://github.com/aio-libs/aiohttp>`_ is like the infamous
 | 
			
		||||
| 
						 | 
				
			
			@ -314,7 +315,7 @@ you can run requests in parallel:
 | 
			
		|||
This code will get the 10 last messages from `@TelethonChat
 | 
			
		||||
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic
 | 
			
		||||
<https://t.me/TelethonOfftopic>`_, and also download the profile
 | 
			
		||||
photo of the main group. asyncio_ will run all these three tasks
 | 
			
		||||
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:
 | 
			
		||||
| 
						 | 
				
			
			@ -355,8 +356,6 @@ Where can I read more?
 | 
			
		|||
======================
 | 
			
		||||
 | 
			
		||||
`Check out my blog post
 | 
			
		||||
<https://lonamiwebs.github.io/blog/asyncio/>`_ about asyncio_, which
 | 
			
		||||
<https://lonamiwebs.github.io/blog/asyncio/>`_ 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -158,12 +158,12 @@ Understanding asyncio
 | 
			
		|||
=====================
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
With ``asyncio``, the library has several tasks running in the background.
 | 
			
		||||
With `asyncio`, the library has several tasks running in the background.
 | 
			
		||||
One task is used for sending requests, another task is used to receive them,
 | 
			
		||||
and a third one is used to handle updates.
 | 
			
		||||
 | 
			
		||||
To handle updates, you must keep your script running. You can do this in
 | 
			
		||||
several ways. For instance, if you are *not* running ``asyncio``'s event
 | 
			
		||||
several ways. For instance, if you are *not* running `asyncio`'s event
 | 
			
		||||
loop, you should use `client.run_until_disconnected
 | 
			
		||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>`:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,9 +40,14 @@ tl_ref_url = 'https://tl.telethon.dev'
 | 
			
		|||
extensions = [
 | 
			
		||||
    'sphinx.ext.autodoc',
 | 
			
		||||
    'sphinx.ext.autosummary',
 | 
			
		||||
    'sphinx.ext.intersphinx',
 | 
			
		||||
    'custom_roles'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
intersphinx_mapping = {
 | 
			
		||||
    'python': ('https://docs.python.org/3', None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Change the default role so we can avoid prefixing everything with :obj:
 | 
			
		||||
default_role = "py:obj"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -195,11 +195,11 @@ banned rights of a user through :tl:`EditBannedRequest` and its parameter
 | 
			
		|||
    client(EditBannedRequest(channel, user, rights))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
You can also use a ``datetime`` object for ``until_date=``, or even a
 | 
			
		||||
Unix timestamp. Note that if you ban someone for less than 30 seconds
 | 
			
		||||
or for more than 366 days, Telegram will consider the ban to actually
 | 
			
		||||
last forever. This is officially documented under
 | 
			
		||||
https://core.telegram.org/bots/api#restrictchatmember.
 | 
			
		||||
You can use a `datetime.datetime` object for ``until_date=``,
 | 
			
		||||
a `datetime.timedelta` or even a Unix timestamp. Note that if you ban
 | 
			
		||||
someone for less than 30 seconds or for more than 366 days, Telegram
 | 
			
		||||
will consider the ban to actually last forever. This is officially
 | 
			
		||||
documented under https://core.telegram.org/bots/api#restrictchatmember.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Kicking a member
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1152,7 +1152,7 @@ reasons. But there's one more surprise!
 | 
			
		|||
 | 
			
		||||
There is a new magic ``telethon.sync`` module to let you use **all** the
 | 
			
		||||
methods in the :ref:`TelegramClient <telethon-client>` (and the types returned
 | 
			
		||||
from its functions) in a synchronous way, while using ``asyncio`` behind
 | 
			
		||||
from its functions) in a synchronous way, while using `asyncio` behind
 | 
			
		||||
the scenes! This means you're now able to do both of the following:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
| 
						 | 
				
			
			@ -1238,7 +1238,7 @@ Bug fixes
 | 
			
		|||
- "User joined" event was being treated as "User was invited".
 | 
			
		||||
- SQLite's cursor should not be closed properly after usage.
 | 
			
		||||
- ``await`` the updates task upon disconnection.
 | 
			
		||||
- Some bug in Python 3.5.2's ``asyncio`` causing 100% CPU load if you
 | 
			
		||||
- Some bug in Python 3.5.2's `asyncio` causing 100% CPU load if you
 | 
			
		||||
  forgot to call `client.disconnect()
 | 
			
		||||
  <telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`.
 | 
			
		||||
  The method is called for you on object destruction, but you still should
 | 
			
		||||
| 
						 | 
				
			
			@ -1373,7 +1373,7 @@ Enhancements
 | 
			
		|||
- ``pathlib.Path`` is now supported for downloading and uploading media.
 | 
			
		||||
- Messages you send to yourself are now considered outgoing, unless they
 | 
			
		||||
  are forwarded.
 | 
			
		||||
- The documentation has been updated with a brand new ``asyncio`` crash
 | 
			
		||||
- The documentation has been updated with a brand new `asyncio` crash
 | 
			
		||||
  course to encourage you use it. You can still use the threaded version
 | 
			
		||||
  if you want though.
 | 
			
		||||
- ``.name`` property is now properly supported when sending and downloading
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
Compatibility and Convenience
 | 
			
		||||
=============================
 | 
			
		||||
 | 
			
		||||
Telethon is an ``asyncio`` library. Compatibility is an important concern,
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ Compatibility
 | 
			
		|||
 | 
			
		||||
Some decisions when developing will inevitable be proven wrong in the future.
 | 
			
		||||
One of these decisions was using threads. Now that Python 3.4 is reaching EOL
 | 
			
		||||
and using ``asyncio`` is usable as of Python 3.5 it makes sense for a library
 | 
			
		||||
and using `asyncio` is usable as of Python 3.5 it makes sense for a library
 | 
			
		||||
like Telethon to make a good use of it.
 | 
			
		||||
 | 
			
		||||
If you have old code, **just use old versions** of the library! There is
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ and clean-ups. Using an older version is the right way to go.
 | 
			
		|||
Sometimes, other small decisions are made. These all will be reflected in the
 | 
			
		||||
:ref:`changelog` which you should read when upgrading.
 | 
			
		||||
 | 
			
		||||
If you want to jump the ``asyncio`` boat, here are some of the things you will
 | 
			
		||||
If you want to jump the `asyncio` boat, here are some of the things you will
 | 
			
		||||
need to start migrating really old code:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +91,7 @@ Convenience
 | 
			
		|||
    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 ``asyncio`` and just work with sequential code. This can prove
 | 
			
		||||
forget about `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
 | 
			
		||||
| 
						 | 
				
			
			@ -178,10 +178,10 @@ 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
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,34 +19,79 @@ its **attributes** (the properties will be shown here).
 | 
			
		|||
.. contents::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CallbackQuery
 | 
			
		||||
NewMessage
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
Occurs whenever a new text message or a message with media arrives.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    The new message event **should be treated as** a
 | 
			
		||||
    normal `Message <telethon.tl.custom.message.Message>`, with
 | 
			
		||||
    the following exceptions:
 | 
			
		||||
 | 
			
		||||
    * ``pattern_match`` is the match object returned by ``pattern=``.
 | 
			
		||||
    * ``message`` is **not** the message string. It's the `Message
 | 
			
		||||
      <telethon.tl.custom.message.Message>` object.
 | 
			
		||||
 | 
			
		||||
    Remember, this event is just a proxy over the message, so while
 | 
			
		||||
    you won't see its attributes and properties, you can still access
 | 
			
		||||
    them. Please see the full documentation for examples.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `NewMessage
 | 
			
		||||
<telethon.events.newmessage.NewMessage>`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageEdited
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Full documentation for the `CallbackQuery
 | 
			
		||||
<telethon.events.callbackquery.CallbackQuery>`.
 | 
			
		||||
Occurs whenever a message is edited. Just like `NewMessage
 | 
			
		||||
<telethon.events.newmessage.NewMessage>`, you should treat
 | 
			
		||||
this event as a `Message <telethon.tl.custom.message.Message>`.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.callbackquery.CallbackQuery.Event
 | 
			
		||||
Full documentation for the `MessageEdited
 | 
			
		||||
<telethon.events.messageedited.MessageEdited>`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageDeleted
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
Occurs whenever a message is deleted. Note that this event isn't 100%
 | 
			
		||||
reliable, since Telegram doesn't always notify the clients that a message
 | 
			
		||||
was deleted.
 | 
			
		||||
 | 
			
		||||
It only has the ``deleted_id`` and ``deleted_ids`` attributes
 | 
			
		||||
(in addition to the chat if the deletion happened in a channel).
 | 
			
		||||
 | 
			
		||||
Full documentation for the `MessageDeleted
 | 
			
		||||
<telethon.events.messagedeleted.MessageDeleted>`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageRead
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Occurs whenever one or more messages are read in a chat.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `MessageRead
 | 
			
		||||
<telethon.events.messageread.MessageRead>`.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.messageread.MessageRead.Event
 | 
			
		||||
 | 
			
		||||
.. autosummary::
 | 
			
		||||
    :nosignatures:
 | 
			
		||||
 | 
			
		||||
        id
 | 
			
		||||
        message_id
 | 
			
		||||
        data
 | 
			
		||||
        chat_instance
 | 
			
		||||
        via_inline
 | 
			
		||||
        inbox
 | 
			
		||||
        message_ids
 | 
			
		||||
 | 
			
		||||
        respond
 | 
			
		||||
        reply
 | 
			
		||||
        edit
 | 
			
		||||
        delete
 | 
			
		||||
        answer
 | 
			
		||||
        get_message
 | 
			
		||||
        get_messages
 | 
			
		||||
        is_read
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ChatAction
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
Occurs whenever a user joins or leaves a chat, or a message is pinned.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `ChatAction
 | 
			
		||||
<telethon.events.chataction.ChatAction>`.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,9 +121,63 @@ Full documentation for the `ChatAction
 | 
			
		|||
        get_input_users
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UserUpdate
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
Occurs whenever a user goes online, starts typing, etc.
 | 
			
		||||
 | 
			
		||||
A lot of fields are attributes and not properties, so they
 | 
			
		||||
are not shown here. Please refer to its full documentation.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `UserUpdate
 | 
			
		||||
<telethon.events.userupdate.UserUpdate>`.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.userupdate.UserUpdate.Event
 | 
			
		||||
 | 
			
		||||
.. autosummary::
 | 
			
		||||
    :nosignatures:
 | 
			
		||||
 | 
			
		||||
        user
 | 
			
		||||
        input_user
 | 
			
		||||
        user_id
 | 
			
		||||
 | 
			
		||||
        get_user
 | 
			
		||||
        get_input_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CallbackQuery
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Occurs whenever you sign in as a bot and a user
 | 
			
		||||
clicks one of the inline buttons on your messages.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `CallbackQuery
 | 
			
		||||
<telethon.events.callbackquery.CallbackQuery>`.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.callbackquery.CallbackQuery.Event
 | 
			
		||||
 | 
			
		||||
.. autosummary::
 | 
			
		||||
    :nosignatures:
 | 
			
		||||
 | 
			
		||||
        id
 | 
			
		||||
        message_id
 | 
			
		||||
        data
 | 
			
		||||
        chat_instance
 | 
			
		||||
        via_inline
 | 
			
		||||
 | 
			
		||||
        respond
 | 
			
		||||
        reply
 | 
			
		||||
        edit
 | 
			
		||||
        delete
 | 
			
		||||
        answer
 | 
			
		||||
        get_message
 | 
			
		||||
 | 
			
		||||
InlineQuery
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Occurs whenever you sign in as a bot and a user
 | 
			
		||||
sends an inline query such as ``@bot query``.
 | 
			
		||||
 | 
			
		||||
Full documentation for the `InlineQuery
 | 
			
		||||
<telethon.events.inlinequery.InlineQuery>`.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,90 +194,9 @@ Full documentation for the `InlineQuery
 | 
			
		|||
 | 
			
		||||
        answer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageDeleted
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
Full documentation for the `MessageDeleted
 | 
			
		||||
<telethon.events.messagedeleted.MessageDeleted>`.
 | 
			
		||||
 | 
			
		||||
It only has the ``deleted_id`` and ``deleted_ids`` attributes
 | 
			
		||||
(in addition to the chat if the deletion happened in a channel).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageEdited
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Full documentation for the `MessageEdited
 | 
			
		||||
<telethon.events.messageedited.MessageEdited>`.
 | 
			
		||||
 | 
			
		||||
This event is the same as `NewMessage
 | 
			
		||||
<telethon.events.newmessage.NewMessage>`,
 | 
			
		||||
but occurs only when an edit happens.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MessageRead
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Full documentation for the `MessageRead
 | 
			
		||||
<telethon.events.messageread.MessageRead>`.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.messageread.MessageRead.Event
 | 
			
		||||
 | 
			
		||||
.. autosummary::
 | 
			
		||||
    :nosignatures:
 | 
			
		||||
 | 
			
		||||
        inbox
 | 
			
		||||
        message_ids
 | 
			
		||||
 | 
			
		||||
        get_messages
 | 
			
		||||
        is_read
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
NewMessage
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
Full documentation for the `NewMessage
 | 
			
		||||
<telethon.events.newmessage.NewMessage>`.
 | 
			
		||||
 | 
			
		||||
Note that the new message event **should be treated as** a
 | 
			
		||||
normal `Message <telethon.tl.custom.message.Message>`, with
 | 
			
		||||
the following exceptions:
 | 
			
		||||
 | 
			
		||||
* ``pattern_match`` is the match object returned by ``pattern=``.
 | 
			
		||||
* ``message`` is **not** the message string. It's the `Message
 | 
			
		||||
  <telethon.tl.custom.message.Message>` object.
 | 
			
		||||
 | 
			
		||||
Remember, this event is just a proxy over the message, so while
 | 
			
		||||
you won't see its attributes and properties, you can still access
 | 
			
		||||
them.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Raw
 | 
			
		||||
===
 | 
			
		||||
 | 
			
		||||
Raw events are not actual events. Instead, they are the raw
 | 
			
		||||
:tl:`Update` object that Telegram sends. You normally shouldn't
 | 
			
		||||
need these.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UserUpdate
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
Full documentation for the `UserUpdate
 | 
			
		||||
<telethon.events.userupdate.UserUpdate>`.
 | 
			
		||||
 | 
			
		||||
A lot of fields are attributes and not properties, so they
 | 
			
		||||
are not shown here.
 | 
			
		||||
 | 
			
		||||
.. currentmodule:: telethon.events.userupdate.UserUpdate.Event
 | 
			
		||||
 | 
			
		||||
.. autosummary::
 | 
			
		||||
    :nosignatures:
 | 
			
		||||
 | 
			
		||||
        user
 | 
			
		||||
        input_user
 | 
			
		||||
        user_id
 | 
			
		||||
 | 
			
		||||
        get_user
 | 
			
		||||
        get_input_user
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -249,7 +249,8 @@ InlineResult
 | 
			
		|||
The `InlineResult <telethon.tl.custom.inlineresult.InlineResult>` object
 | 
			
		||||
is returned inside a list by the `client.inline_query()
 | 
			
		||||
<telethon.client.bots.BotMethods.inline_query>` method to make an inline
 | 
			
		||||
query to a bot that supports being used in inline mode, such as ``@like``.
 | 
			
		||||
query to a bot that supports being used in inline mode, such as
 | 
			
		||||
`@like <https://t.me/like>`_.
 | 
			
		||||
 | 
			
		||||
Note that the list returned is in fact a *subclass* of a list called
 | 
			
		||||
`InlineResults <telethon.tl.custom.inlineresults.InlineResults>`, which,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -282,11 +282,12 @@ class AuthMethods(MessageParseMethods, UserMethods):
 | 
			
		|||
 | 
			
		||||
            password (`str`):
 | 
			
		||||
                2FA password, should be used if a previous call raised
 | 
			
		||||
                SessionPasswordNeededError.
 | 
			
		||||
                ``SessionPasswordNeededError``.
 | 
			
		||||
 | 
			
		||||
            bot_token (`str`):
 | 
			
		||||
                Used to sign in as a bot. Not all requests will be available.
 | 
			
		||||
                This should be the hash the @BotFather gave you.
 | 
			
		||||
                This should be the hash the `@BotFather <https://t.me/BotFather>`_
 | 
			
		||||
                gave you.
 | 
			
		||||
 | 
			
		||||
            phone (`str` | `int`):
 | 
			
		||||
                By default, the library remembers the phone passed to
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ if typing.TYPE_CHECKING:
 | 
			
		|||
 | 
			
		||||
class _DialogsIter(RequestIter):
 | 
			
		||||
    async def _init(
 | 
			
		||||
            self, offset_date, offset_id, offset_peer, ignore_migrated, folder
 | 
			
		||||
            self, offset_date, offset_id, offset_peer, ignore_pinned, ignore_migrated, folder
 | 
			
		||||
    ):
 | 
			
		||||
        self.request = functions.messages.GetDialogsRequest(
 | 
			
		||||
            offset_date=offset_date,
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ class _DialogsIter(RequestIter):
 | 
			
		|||
            offset_peer=offset_peer,
 | 
			
		||||
            limit=1,
 | 
			
		||||
            hash=0,
 | 
			
		||||
            exclude_pinned=ignore_pinned,
 | 
			
		||||
            folder_id=folder
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,15 +77,19 @@ class _DialogsIter(RequestIter):
 | 
			
		|||
            # we didn't get a DialogsSlice which means we got all.
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        # Don't set `request.offset_id` to the last message ID.
 | 
			
		||||
        # Why? It seems to cause plenty of dialogs to be skipped.
 | 
			
		||||
        #
 | 
			
		||||
        # By leaving it to 0 after the first iteration, even if
 | 
			
		||||
        # the user originally passed another ID, we ensure that
 | 
			
		||||
        # it will work correctly.
 | 
			
		||||
        self.request.offset_id = 0
 | 
			
		||||
        # We can't use `messages[-1]` as the offset ID / date.
 | 
			
		||||
        # Why? Because pinned dialogs will mess with the order
 | 
			
		||||
        # in this list. Instead, we find the last dialog which
 | 
			
		||||
        # has a message, and use it as an offset.
 | 
			
		||||
        last_message = next((
 | 
			
		||||
            messages[d.top_message]
 | 
			
		||||
            for d in reversed(r.dialogs)
 | 
			
		||||
            if d.top_message in messages
 | 
			
		||||
        ), None)
 | 
			
		||||
 | 
			
		||||
        self.request.exclude_pinned = True
 | 
			
		||||
        self.request.offset_date = r.messages[-1].date
 | 
			
		||||
        self.request.offset_id = last_message.id if last_message else 0
 | 
			
		||||
        self.request.offset_date = last_message.date if last_message else None
 | 
			
		||||
        self.request.offset_peer =\
 | 
			
		||||
            entities[utils.get_peer_id(r.dialogs[-1].peer)]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +115,7 @@ class DialogMethods(UserMethods):
 | 
			
		|||
            offset_date: 'hints.DateLike' = None,
 | 
			
		||||
            offset_id: int = 0,
 | 
			
		||||
            offset_peer: 'hints.EntityLike' = types.InputPeerEmpty(),
 | 
			
		||||
            ignore_pinned: bool = False,
 | 
			
		||||
            ignore_migrated: bool = False,
 | 
			
		||||
            folder: int = None,
 | 
			
		||||
            archived: bool = None
 | 
			
		||||
| 
						 | 
				
			
			@ -134,11 +140,15 @@ class DialogMethods(UserMethods):
 | 
			
		|||
            offset_peer (:tl:`InputPeer`, optional):
 | 
			
		||||
                The peer to be used as an offset.
 | 
			
		||||
 | 
			
		||||
            ignore_pinned (`bool`, optional):
 | 
			
		||||
                Whether pinned dialogs should be ignored or not.
 | 
			
		||||
                When set to ``True``, these won't be yielded at all.
 | 
			
		||||
 | 
			
		||||
            ignore_migrated (`bool`, optional):
 | 
			
		||||
                Whether :tl:`Chat` that have ``migrated_to`` a :tl:`Channel`
 | 
			
		||||
                should be included or not. By default all the chats in your
 | 
			
		||||
                dialogs are returned, but setting this to ``True`` will hide
 | 
			
		||||
                them in the same way official applications do.
 | 
			
		||||
                dialogs are returned, but setting this to ``True`` will ignore
 | 
			
		||||
                (i.e. skip) them in the same way official applications do.
 | 
			
		||||
 | 
			
		||||
            folder (`int`, optional):
 | 
			
		||||
                The folder from which the dialogs should be retrieved.
 | 
			
		||||
| 
						 | 
				
			
			@ -178,6 +188,7 @@ class DialogMethods(UserMethods):
 | 
			
		|||
            offset_date=offset_date,
 | 
			
		||||
            offset_id=offset_id,
 | 
			
		||||
            offset_peer=offset_peer,
 | 
			
		||||
            ignore_pinned=ignore_pinned,
 | 
			
		||||
            ignore_migrated=ignore_migrated,
 | 
			
		||||
            folder=folder
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import hashlib
 | 
			
		||||
import io
 | 
			
		||||
import itertools
 | 
			
		||||
import os
 | 
			
		||||
import pathlib
 | 
			
		||||
import re
 | 
			
		||||
| 
						 | 
				
			
			@ -78,9 +79,9 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods):
 | 
			
		|||
    async def send_file(
 | 
			
		||||
            self: 'TelegramClient',
 | 
			
		||||
            entity: 'hints.EntityLike',
 | 
			
		||||
            file: 'hints.FileLike',
 | 
			
		||||
            file: 'typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]',
 | 
			
		||||
            *,
 | 
			
		||||
            caption: str = None,
 | 
			
		||||
            caption: typing.Union[str, typing.Sequence[str]] = None,
 | 
			
		||||
            force_document: bool = False,
 | 
			
		||||
            progress_callback: 'hints.ProgressCallback' = None,
 | 
			
		||||
            reply_to: 'hints.MessageIDLike' = None,
 | 
			
		||||
| 
						 | 
				
			
			@ -244,31 +245,41 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods):
 | 
			
		|||
        # First check if the user passed an iterable, in which case
 | 
			
		||||
        # we may want to send as an album if all are photo files.
 | 
			
		||||
        if utils.is_list_like(file):
 | 
			
		||||
            image_captions = []
 | 
			
		||||
            document_captions = []
 | 
			
		||||
            if utils.is_list_like(caption):
 | 
			
		||||
                captions = caption
 | 
			
		||||
            else:
 | 
			
		||||
                captions = [caption]
 | 
			
		||||
 | 
			
		||||
            # TODO Fix progress_callback
 | 
			
		||||
            images = []
 | 
			
		||||
            if force_document:
 | 
			
		||||
                documents = file
 | 
			
		||||
            else:
 | 
			
		||||
                documents = []
 | 
			
		||||
                for x in file:
 | 
			
		||||
                    if utils.is_image(x):
 | 
			
		||||
                        images.append(x)
 | 
			
		||||
                for doc, cap in itertools.zip_longest(file, captions):
 | 
			
		||||
                    if utils.is_image(doc):
 | 
			
		||||
                        images.append(doc)
 | 
			
		||||
                        image_captions.append(cap)
 | 
			
		||||
                    else:
 | 
			
		||||
                        documents.append(x)
 | 
			
		||||
                        documents.append(doc)
 | 
			
		||||
                        document_captions.append(cap)
 | 
			
		||||
 | 
			
		||||
            result = []
 | 
			
		||||
            while images:
 | 
			
		||||
                result += await self._send_album(
 | 
			
		||||
                    entity, images[:10], caption=caption,
 | 
			
		||||
                    entity, images[:10], caption=image_captions[:10],
 | 
			
		||||
                    progress_callback=progress_callback, reply_to=reply_to,
 | 
			
		||||
                    parse_mode=parse_mode, silent=silent
 | 
			
		||||
                )
 | 
			
		||||
                images = images[10:]
 | 
			
		||||
                image_captions = image_captions[10:]
 | 
			
		||||
 | 
			
		||||
            for x in documents:
 | 
			
		||||
            for doc, cap in zip(documents, captions):
 | 
			
		||||
                result.append(await self.send_file(
 | 
			
		||||
                    entity, x,
 | 
			
		||||
                    caption=caption, force_document=force_document,
 | 
			
		||||
                    entity, doc,
 | 
			
		||||
                    caption=cap, force_document=force_document,
 | 
			
		||||
                    progress_callback=progress_callback, reply_to=reply_to,
 | 
			
		||||
                    attributes=attributes, thumb=thumb, voice_note=voice_note,
 | 
			
		||||
                    video_note=video_note, buttons=buttons, silent=silent,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,23 +3,37 @@ Helper module around the system's libssl library if available for IGE mode.
 | 
			
		|||
"""
 | 
			
		||||
import ctypes
 | 
			
		||||
import ctypes.util
 | 
			
		||||
try:
 | 
			
		||||
    import ctypes.macholib.dyld
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
__log__ = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lib = ctypes.util.find_library('ssl')
 | 
			
		||||
def _find_ssl_lib():
 | 
			
		||||
    lib = ctypes.util.find_library('ssl')
 | 
			
		||||
    if not lib:
 | 
			
		||||
        raise OSError('no library called "ssl" found')
 | 
			
		||||
 | 
			
		||||
# This is a best-effort attempt at finding the full real path of lib.
 | 
			
		||||
#
 | 
			
		||||
# Unfortunately ctypes doesn't tell us *where* it finds the library,
 | 
			
		||||
# so we have to do that ourselves.
 | 
			
		||||
try:
 | 
			
		||||
    # First, let ctypes try to handle it itself.
 | 
			
		||||
    try:
 | 
			
		||||
        libssl = ctypes.cdll.LoadLibrary(lib)
 | 
			
		||||
    except OSError:
 | 
			
		||||
        pass
 | 
			
		||||
    else:
 | 
			
		||||
        return libssl
 | 
			
		||||
 | 
			
		||||
    # This is a best-effort attempt at finding the full real path of lib.
 | 
			
		||||
    #
 | 
			
		||||
    # Unfortunately ctypes doesn't tell us *where* it finds the library,
 | 
			
		||||
    # so we have to do that ourselves.
 | 
			
		||||
    try:
 | 
			
		||||
        # This is not documented, so it could fail. Be on the safe side.
 | 
			
		||||
    import ctypes.macholib.dyld
 | 
			
		||||
        paths = ctypes.macholib.dyld.DEFAULT_LIBRARY_FALLBACK
 | 
			
		||||
except (ImportError, AttributeError):
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        paths = [
 | 
			
		||||
            os.path.expanduser("~/lib"),
 | 
			
		||||
            "/usr/local/lib",
 | 
			
		||||
| 
						 | 
				
			
			@ -27,25 +41,24 @@ except (ImportError, AttributeError):
 | 
			
		|||
            "/usr/lib",
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
for path in paths:
 | 
			
		||||
    for path in paths:
 | 
			
		||||
        if os.path.isdir(path):
 | 
			
		||||
            for root, _, files in os.walk(path):
 | 
			
		||||
                if lib in files:
 | 
			
		||||
                    # Manually follow symbolic links on *nix systems.
 | 
			
		||||
                    # Fix for https://github.com/LonamiWebs/Telethon/issues/1167
 | 
			
		||||
                    lib = os.path.realpath(os.path.join(root, lib))
 | 
			
		||||
                break
 | 
			
		||||
                    return ctypes.cdll.LoadLibrary(lib)
 | 
			
		||||
    else:
 | 
			
		||||
        raise OSError('no absolute path for "%s" and cannot load by name' % lib)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    if not lib:
 | 
			
		||||
        raise OSError('no library called "ssl" found')
 | 
			
		||||
 | 
			
		||||
    _libssl = ctypes.cdll.LoadLibrary(lib)
 | 
			
		||||
    _libssl = _find_ssl_lib()
 | 
			
		||||
except OSError as e:
 | 
			
		||||
    # See https://github.com/LonamiWebs/Telethon/issues/1167
 | 
			
		||||
    # Sometimes `find_library` returns improper filenames.
 | 
			
		||||
    __log__.info('Failed to load %s: %s (%s)', lib, type(e), e)
 | 
			
		||||
    __log__.info('Failed to load SSL library: %s (%s)', type(e), e)
 | 
			
		||||
    _libssl = None
 | 
			
		||||
 | 
			
		||||
if not _libssl:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,8 @@ from ..tl.custom.sendergetter import SenderGetter
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class CallbackQuery(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a callback query event (when an inline button is clicked).
 | 
			
		||||
    Occurs whenever you sign in as a bot and a user
 | 
			
		||||
    clicks one of the inline buttons on your messages.
 | 
			
		||||
 | 
			
		||||
    Note that the `chats` parameter will **not** work with normal
 | 
			
		||||
    IDs or peers if the clicked inline button comes from a "via bot"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ from ..tl import types, functions
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class ChatAction(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents an action in a chat (such as user joined, left, or new pin).
 | 
			
		||||
    Occurs whenever a user joins or leaves a chat, or a message is pinned.
 | 
			
		||||
    """
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def build(cls, update):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,8 @@ from ..tl.custom.sendergetter import SenderGetter
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class InlineQuery(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents an inline query event (when someone writes ``'@my_bot query'``).
 | 
			
		||||
    Occurs whenever you sign in as a bot and a user
 | 
			
		||||
    sends an inline query such as ``@bot query``.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        users (`entity`, optional):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,9 @@ from ..tl import types
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class MessageDeleted(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Event fired when one or more messages are deleted.
 | 
			
		||||
    Occurs whenever a message is deleted. Note that this event isn't 100%
 | 
			
		||||
    reliable, since Telegram doesn't always notify the clients that a message
 | 
			
		||||
    was deleted.
 | 
			
		||||
 | 
			
		||||
    .. important::
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,9 @@ from ..tl import types
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class MessageEdited(NewMessage):
 | 
			
		||||
    """
 | 
			
		||||
    Event fired when a message has been edited.
 | 
			
		||||
    Occurs whenever a message is edited. Just like `NewMessage
 | 
			
		||||
    <telethon.events.newmessage.NewMessage>`, you should treat
 | 
			
		||||
    this event as a `Message <telethon.tl.custom.message.Message>`.
 | 
			
		||||
 | 
			
		||||
    .. warning::
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ from ..tl import types
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class MessageRead(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Event fired when one or more messages have been read.
 | 
			
		||||
    Occurs whenever one or more messages are read in a chat.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        inbox (`bool`, optional):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ from ..tl import types
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class NewMessage(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a new message event builder.
 | 
			
		||||
    Occurs whenever a new text message or a message with media arrives.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        incoming (`bool`, optional):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,9 @@ from .. import utils
 | 
			
		|||
 | 
			
		||||
class Raw(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a raw event. The event is the update itself.
 | 
			
		||||
    Raw events are not actual events. Instead, they are the raw
 | 
			
		||||
    :tl:`Update` object that Telegram sends. You normally shouldn't
 | 
			
		||||
    need these.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        types (`list` | `tuple` | `type`, optional):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ from ..tl.custom.sendergetter import SenderGetter
 | 
			
		|||
@name_inner_event
 | 
			
		||||
class UserUpdate(EventBuilder):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a user update (gone online, offline, joined Telegram).
 | 
			
		||||
    Occurs whenever a user goes online, starts typing, etc.
 | 
			
		||||
    """
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def build(cls, update):
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +32,8 @@ class UserUpdate(EventBuilder):
 | 
			
		|||
 | 
			
		||||
    class Event(EventCommon, SenderGetter):
 | 
			
		||||
        """
 | 
			
		||||
        Represents the event of a user status update (last seen, joined).
 | 
			
		||||
        Represents the event of a user update
 | 
			
		||||
        such as gone online, started typing, etc.
 | 
			
		||||
 | 
			
		||||
        Members:
 | 
			
		||||
            online (`bool`, optional):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -544,6 +544,12 @@ class MTProtoSender:
 | 
			
		|||
            await self._process_message(message)
 | 
			
		||||
 | 
			
		||||
    async def _handle_update(self, message):
 | 
			
		||||
        try:
 | 
			
		||||
            assert message.obj.SUBCLASS_OF_ID == 0x8af52aac  # crc32(b'Updates')
 | 
			
		||||
        except AssertionError:
 | 
			
		||||
            self._log.warning('Note: %s is not an update, not dispatching it %s', message.obj)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self._log.debug('Handling update %s', message.obj.__class__.__name__)
 | 
			
		||||
        if self._update_callback:
 | 
			
		||||
            self._update_callback(message.obj)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -285,11 +285,12 @@ class Conversation(ChatGetter):
 | 
			
		|||
        return await self._get_result(future, start_time, timeout, self._custom, counter)
 | 
			
		||||
 | 
			
		||||
    async def _check_custom(self, built):
 | 
			
		||||
        for i, (ev, fut) in self._custom.items():
 | 
			
		||||
        for key, (ev, fut) in list(self._custom.items()):
 | 
			
		||||
            ev_type = type(ev)
 | 
			
		||||
            inst = built[ev_type]
 | 
			
		||||
            if inst and ev.filter(inst):
 | 
			
		||||
                fut.set_result(inst)
 | 
			
		||||
                del self._custom[key]
 | 
			
		||||
 | 
			
		||||
    def _on_new_message(self, response):
 | 
			
		||||
        response = response.message
 | 
			
		||||
| 
						 | 
				
			
			@ -302,22 +303,28 @@ class Conversation(ChatGetter):
 | 
			
		|||
 | 
			
		||||
        self._incoming.append(response)
 | 
			
		||||
 | 
			
		||||
        # Note: we don't remove from pending here, that's done on get result
 | 
			
		||||
        for msg_id, future in self._pending_responses.items():
 | 
			
		||||
        # Most of the time, these dictionaries will contain just one item
 | 
			
		||||
        # TODO In fact, why not make it be that way? Force one item only.
 | 
			
		||||
        #      How often will people want to wait for two responses at
 | 
			
		||||
        #      the same time? It's impossible, first one will arrive
 | 
			
		||||
        #      and then another, so they can do that.
 | 
			
		||||
        for msg_id, future in list(self._pending_responses.items()):
 | 
			
		||||
            self._response_indices[msg_id] = len(self._incoming)
 | 
			
		||||
            future.set_result(response)
 | 
			
		||||
            del self._pending_responses[msg_id]
 | 
			
		||||
 | 
			
		||||
        for msg_id, future in self._pending_replies.items():
 | 
			
		||||
        for msg_id, future in list(self._pending_replies.items()):
 | 
			
		||||
            if msg_id == response.reply_to_msg_id:
 | 
			
		||||
                self._reply_indices[msg_id] = len(self._incoming)
 | 
			
		||||
                future.set_result(response)
 | 
			
		||||
                del self._pending_replies[msg_id]
 | 
			
		||||
 | 
			
		||||
    def _on_edit(self, message):
 | 
			
		||||
        message = message.message
 | 
			
		||||
        if message.chat_id != self.chat_id or message.out:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for msg_id, future in self._pending_edits.items():
 | 
			
		||||
        for msg_id, future in list(self._pending_edits.items()):
 | 
			
		||||
            if msg_id < message.id:
 | 
			
		||||
                edit_ts = message.edit_date.timestamp()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -330,6 +337,7 @@ class Conversation(ChatGetter):
 | 
			
		|||
                    self._edit_dates[msg_id] = message.edit_date.timestamp()
 | 
			
		||||
 | 
			
		||||
                future.set_result(message)
 | 
			
		||||
                del self._pending_edits[msg_id]
 | 
			
		||||
 | 
			
		||||
    def _on_read(self, event):
 | 
			
		||||
        if event.chat_id != self.chat_id or event.inbox:
 | 
			
		||||
| 
						 | 
				
			
			@ -338,10 +346,11 @@ class Conversation(ChatGetter):
 | 
			
		|||
        self._last_read = event.max_id
 | 
			
		||||
 | 
			
		||||
        remove_reads = []
 | 
			
		||||
        for msg_id, pending in self._pending_reads.items():
 | 
			
		||||
        for msg_id, pending in list(self._pending_reads.items()):
 | 
			
		||||
            if msg_id >= self._last_read:
 | 
			
		||||
                remove_reads.append(msg_id)
 | 
			
		||||
                pending.set_result(True)
 | 
			
		||||
                del self._pending_reads[msg_id]
 | 
			
		||||
 | 
			
		||||
        for to_remove in remove_reads:
 | 
			
		||||
            del self._pending_reads[to_remove]
 | 
			
		||||
| 
						 | 
				
			
			@ -365,14 +374,16 @@ class Conversation(ChatGetter):
 | 
			
		|||
        if timeout is not None:
 | 
			
		||||
            due = min(due, start_time + timeout)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
        # NOTE: We can't try/finally to pop from pending here because
 | 
			
		||||
        #       the event loop needs to get back to us, but it might
 | 
			
		||||
        #       dispatch another update before, and in that case a
 | 
			
		||||
        #       response could be set twice. So responses must be
 | 
			
		||||
        #       cleared when their futures are set to a result.
 | 
			
		||||
        return await asyncio.wait_for(
 | 
			
		||||
            future,
 | 
			
		||||
            timeout=None if due == float('inf') else due - time.time(),
 | 
			
		||||
            loop=self._client.loop
 | 
			
		||||
        )
 | 
			
		||||
        finally:
 | 
			
		||||
            del pending[target_id]
 | 
			
		||||
 | 
			
		||||
    def _cancel_all(self, exception=None):
 | 
			
		||||
        self._cancelled = True
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -855,6 +855,16 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
 | 
			
		|||
        else:
 | 
			
		||||
            return await self._buttons[i][j].click()
 | 
			
		||||
 | 
			
		||||
    async def mark_read(self):
 | 
			
		||||
        """
 | 
			
		||||
        Marks the message as read. Shorthand for
 | 
			
		||||
        `client.send_read_acknowledge()
 | 
			
		||||
        <telethon.client.messages.MessageMethods.send_read_acknowledge>`
 | 
			
		||||
        with both ``entity`` and ``message`` already set.
 | 
			
		||||
        """
 | 
			
		||||
        await self._client.send_read_acknowledge(
 | 
			
		||||
            await self.get_input_chat(), max_id=self.id)
 | 
			
		||||
 | 
			
		||||
    async def pin(self, *, notify=False):
 | 
			
		||||
        """
 | 
			
		||||
        Pins the message. Shorthand for
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,8 +106,12 @@ def get_extension(media):
 | 
			
		|||
    """Gets the corresponding extension for any Telegram media."""
 | 
			
		||||
 | 
			
		||||
    # Photos are always compressed as .jpg by Telegram
 | 
			
		||||
    if isinstance(media, (types.UserProfilePhoto,
 | 
			
		||||
                          types.ChatPhoto, types.MessageMediaPhoto)):
 | 
			
		||||
    try:
 | 
			
		||||
        get_input_photo(media)
 | 
			
		||||
        return '.jpg'
 | 
			
		||||
    except TypeError:
 | 
			
		||||
        # These cases are not handled by input photo because it can't
 | 
			
		||||
        if isinstance(media, (types.UserProfilePhoto, types.ChatPhoto)):
 | 
			
		||||
            return '.jpg'
 | 
			
		||||
 | 
			
		||||
    # Documents will come with a mime type
 | 
			
		||||
| 
						 | 
				
			
			@ -290,7 +294,10 @@ def get_input_photo(photo):
 | 
			
		|||
    except AttributeError:
 | 
			
		||||
        _raise_cast_fail(photo, 'InputPhoto')
 | 
			
		||||
 | 
			
		||||
    if isinstance(photo, types.photos.Photo):
 | 
			
		||||
    if isinstance(photo, types.Message):
 | 
			
		||||
        photo = photo.media
 | 
			
		||||
 | 
			
		||||
    if isinstance(photo, (types.photos.Photo, types.MessageMediaPhoto)):
 | 
			
		||||
        photo = photo.photo
 | 
			
		||||
 | 
			
		||||
    if isinstance(photo, types.Photo):
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +309,7 @@ def get_input_photo(photo):
 | 
			
		|||
 | 
			
		||||
    if isinstance(photo, types.messages.ChatFull):
 | 
			
		||||
        photo = photo.full_chat
 | 
			
		||||
 | 
			
		||||
    if isinstance(photo, types.ChannelFull):
 | 
			
		||||
        return get_input_photo(photo.chat_photo)
 | 
			
		||||
    elif isinstance(photo, types.UserFull):
 | 
			
		||||
| 
						 | 
				
			
			@ -678,7 +686,8 @@ def _get_extension(file):
 | 
			
		|||
        # Note: ``file.name`` works for :tl:`InputFile` and some `IOBase`
 | 
			
		||||
        return _get_extension(file.name)
 | 
			
		||||
    else:
 | 
			
		||||
        return ''
 | 
			
		||||
        # Maybe it's a Telegram media
 | 
			
		||||
        return get_extension(file)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_image(file):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,8 +171,22 @@ function updateSearch(event) {
 | 
			
		|||
    var foundTypes = getSearchArray(types, typesu, query);
 | 
			
		||||
    var foundConstructors = getSearchArray(constructors, constructorsu, query);
 | 
			
		||||
 | 
			
		||||
    var original = requests.concat(constructors);
 | 
			
		||||
    var originalu = requestsu.concat(constructorsu);
 | 
			
		||||
    var destination = [];
 | 
			
		||||
    var destinationu = [];
 | 
			
		||||
 | 
			
		||||
    for (var i = 0; i < original.length; ++i) {
 | 
			
		||||
        if (original[i].toLowerCase().replace("request", "") == query) {
 | 
			
		||||
            destination.push(original[i]);
 | 
			
		||||
            destinationu.push(originalu[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (event && event.keyCode == 13) {
 | 
			
		||||
        if (methodsDetails.open && foundRequests[1].length) {
 | 
			
		||||
        if (destination.length != 0) {
 | 
			
		||||
            window.location = destinationu[0];
 | 
			
		||||
        } else if (methodsDetails.open && foundRequests[1].length) {
 | 
			
		||||
            window.location = foundRequests[1][0];
 | 
			
		||||
        } else if (typesDetails.open && foundTypes[1].length) {
 | 
			
		||||
            window.location = foundTypes[1][0];
 | 
			
		||||
| 
						 | 
				
			
			@ -187,18 +201,6 @@ function updateSearch(event) {
 | 
			
		|||
    buildList(constructorsCount, constructorsList, foundConstructors);
 | 
			
		||||
 | 
			
		||||
    // Now look for exact matches
 | 
			
		||||
    var original = requests.concat(constructors);
 | 
			
		||||
    var originalu = requestsu.concat(constructorsu);
 | 
			
		||||
    var destination = [];
 | 
			
		||||
    var destinationu = [];
 | 
			
		||||
 | 
			
		||||
    for (var i = 0; i < original.length; ++i) {
 | 
			
		||||
        if (original[i].toLowerCase().replace("request", "") == query) {
 | 
			
		||||
            destination.push(original[i]);
 | 
			
		||||
            destinationu.push(originalu[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (destination.length == 0) {
 | 
			
		||||
        exactMatch.style.display = "none";
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user