mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-10 16:12:22 +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.
|
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
|
.. _pyaes: https://github.com/ricmoo/pyaes
|
||||||
.. _pillow: https://python-pillow.org
|
.. _pillow: https://python-pillow.org
|
||||||
.. _aiohttp: https://docs.aiohttp.org
|
.. _aiohttp: https://docs.aiohttp.org
|
||||||
|
|
|
@ -140,3 +140,54 @@ where the following keys are allowed:
|
||||||
}
|
}
|
||||||
|
|
||||||
.. __: https://github.com/nibrag/aiosocks
|
.. __: 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::
|
.. note::
|
||||||
|
|
||||||
Event handlers **must** be ``async def``. After all,
|
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.
|
which is a safer and often faster approach to threads.
|
||||||
|
|
||||||
You **must** ``await`` all method calls that use
|
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
|
As a rule of thumb, remember that new message events behave just
|
||||||
like message objects, so you can do with them everything you can
|
like message objects, so you can do with them everything you can
|
||||||
do with a message object.
|
do with a message object.
|
||||||
|
|
||||||
.. _asyncio: https://docs.python.org/3/library/asyncio.html
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ Mastering asyncio
|
||||||
What's 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
|
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.
|
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
|
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.
|
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.
|
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
|
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
|
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
|
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.
|
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
|
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
|
whenever you use the client, or enable the compatible mode. For that, see
|
||||||
:ref:`compatibility-and-convenience`.
|
:ref:`compatibility-and-convenience`.
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ client.run_until_disconnected() blocks!
|
||||||
|
|
||||||
All of what `client.run_until_disconnected()
|
All of what `client.run_until_disconnected()
|
||||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` does is
|
<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
|
*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:
|
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.
|
This creates a task for a clock that prints the time every second.
|
||||||
You don't need to use `client.run_until_disconnected()
|
You don't need to use `client.run_until_disconnected()
|
||||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>` either!
|
<telethon.client.updates.UpdateMethods.run_until_disconnected>` either!
|
||||||
You just need to make the loop is running, somehow. ``asyncio.run_forever``
|
You just need to make the loop is running, somehow. `loop.run_forever()
|
||||||
and ``asyncio.run_until_complete`` can also be used to run the loop, and
|
<asyncio.loop.run_forever()>` and `loop.run_until_complete()
|
||||||
Telethon will be happy with any approach.
|
<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.
|
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
|
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.
|
with Telethon.
|
||||||
|
|
||||||
* `aiohttp <https://github.com/aio-libs/aiohttp>`_ is like the infamous
|
* `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
|
This code will get the 10 last messages from `@TelethonChat
|
||||||
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic
|
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic
|
||||||
<https://t.me/TelethonOfftopic>`_, and also download the profile
|
<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.
|
at the same time. You can run all the tasks you want this way.
|
||||||
|
|
||||||
A different way would be:
|
A different way would be:
|
||||||
|
@ -355,8 +356,6 @@ Where can I read more?
|
||||||
======================
|
======================
|
||||||
|
|
||||||
`Check out my blog post
|
`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
|
has some more examples and pictures to help you understand what happens
|
||||||
when the loop runs.
|
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,
|
One task is used for sending requests, another task is used to receive them,
|
||||||
and a third one is used to handle updates.
|
and a third one is used to handle updates.
|
||||||
|
|
||||||
To handle updates, you must keep your script running. You can do this in
|
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
|
loop, you should use `client.run_until_disconnected
|
||||||
<telethon.client.updates.UpdateMethods.run_until_disconnected>`:
|
<telethon.client.updates.UpdateMethods.run_until_disconnected>`:
|
||||||
|
|
||||||
|
|
|
@ -40,9 +40,14 @@ tl_ref_url = 'https://tl.telethon.dev'
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.autosummary',
|
'sphinx.ext.autosummary',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
'custom_roles'
|
'custom_roles'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'python': ('https://docs.python.org/3', None)
|
||||||
|
}
|
||||||
|
|
||||||
# Change the default role so we can avoid prefixing everything with :obj:
|
# Change the default role so we can avoid prefixing everything with :obj:
|
||||||
default_role = "py: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))
|
client(EditBannedRequest(channel, user, rights))
|
||||||
|
|
||||||
|
|
||||||
You can also use a ``datetime`` object for ``until_date=``, or even a
|
You can use a `datetime.datetime` object for ``until_date=``,
|
||||||
Unix timestamp. Note that if you ban someone for less than 30 seconds
|
a `datetime.timedelta` or even a Unix timestamp. Note that if you ban
|
||||||
or for more than 366 days, Telegram will consider the ban to actually
|
someone for less than 30 seconds or for more than 366 days, Telegram
|
||||||
last forever. This is officially documented under
|
will consider the ban to actually last forever. This is officially
|
||||||
https://core.telegram.org/bots/api#restrictchatmember.
|
documented under https://core.telegram.org/bots/api#restrictchatmember.
|
||||||
|
|
||||||
|
|
||||||
Kicking a member
|
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
|
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
|
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:
|
the scenes! This means you're now able to do both of the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -1238,7 +1238,7 @@ Bug fixes
|
||||||
- "User joined" event was being treated as "User was invited".
|
- "User joined" event was being treated as "User was invited".
|
||||||
- SQLite's cursor should not be closed properly after usage.
|
- SQLite's cursor should not be closed properly after usage.
|
||||||
- ``await`` the updates task upon disconnection.
|
- ``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()
|
forgot to call `client.disconnect()
|
||||||
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`.
|
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`.
|
||||||
The method is called for you on object destruction, but you still should
|
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.
|
- ``pathlib.Path`` is now supported for downloading and uploading media.
|
||||||
- Messages you send to yourself are now considered outgoing, unless they
|
- Messages you send to yourself are now considered outgoing, unless they
|
||||||
are forwarded.
|
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
|
course to encourage you use it. You can still use the threaded version
|
||||||
if you want though.
|
if you want though.
|
||||||
- ``.name`` property is now properly supported when sending and downloading
|
- ``.name`` property is now properly supported when sending and downloading
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
Compatibility and Convenience
|
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`
|
and while it can't always be kept and mistakes happens, the :ref:`changelog`
|
||||||
is there to tell you when these important changes happen.
|
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.
|
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
|
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.
|
like Telethon to make a good use of it.
|
||||||
|
|
||||||
If you have old code, **just use old versions** of the library! There is
|
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
|
Sometimes, other small decisions are made. These all will be reflected in the
|
||||||
:ref:`changelog` which you should read when upgrading.
|
: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:
|
need to start migrating really old code:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -91,7 +91,7 @@ Convenience
|
||||||
This makes the examples shorter and easier to think about.
|
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
|
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.
|
to be a powerful hybrid for running under the Python REPL too.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -178,10 +178,10 @@ overhead you pay if you import it, and what you save if you don't.
|
||||||
Learning
|
Learning
|
||||||
========
|
========
|
||||||
|
|
||||||
You know the library uses ``asyncio`` everywhere, and you want to learn
|
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
|
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
|
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
|
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.
|
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::
|
.. 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
|
Occurs whenever a message is edited. Just like `NewMessage
|
||||||
<telethon.events.callbackquery.CallbackQuery>`.
|
<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::
|
.. autosummary::
|
||||||
:nosignatures:
|
:nosignatures:
|
||||||
|
|
||||||
id
|
inbox
|
||||||
message_id
|
message_ids
|
||||||
data
|
|
||||||
chat_instance
|
|
||||||
via_inline
|
|
||||||
|
|
||||||
respond
|
get_messages
|
||||||
reply
|
is_read
|
||||||
edit
|
|
||||||
delete
|
|
||||||
answer
|
|
||||||
get_message
|
|
||||||
|
|
||||||
|
|
||||||
ChatAction
|
ChatAction
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
Occurs whenever a user joins or leaves a chat, or a message is pinned.
|
||||||
|
|
||||||
Full documentation for the `ChatAction
|
Full documentation for the `ChatAction
|
||||||
<telethon.events.chataction.ChatAction>`.
|
<telethon.events.chataction.ChatAction>`.
|
||||||
|
|
||||||
|
@ -76,9 +121,63 @@ Full documentation for the `ChatAction
|
||||||
get_input_users
|
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
|
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
|
Full documentation for the `InlineQuery
|
||||||
<telethon.events.inlinequery.InlineQuery>`.
|
<telethon.events.inlinequery.InlineQuery>`.
|
||||||
|
|
||||||
|
@ -95,90 +194,9 @@ Full documentation for the `InlineQuery
|
||||||
|
|
||||||
answer
|
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
|
||||||
===
|
===
|
||||||
|
|
||||||
Raw events are not actual events. Instead, they are the raw
|
Raw events are not actual events. Instead, they are the raw
|
||||||
:tl:`Update` object that Telegram sends. You normally shouldn't
|
:tl:`Update` object that Telegram sends. You normally shouldn't
|
||||||
need these.
|
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
|
The `InlineResult <telethon.tl.custom.inlineresult.InlineResult>` object
|
||||||
is returned inside a list by the `client.inline_query()
|
is returned inside a list by the `client.inline_query()
|
||||||
<telethon.client.bots.BotMethods.inline_query>` method to make an inline
|
<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
|
Note that the list returned is in fact a *subclass* of a list called
|
||||||
`InlineResults <telethon.tl.custom.inlineresults.InlineResults>`, which,
|
`InlineResults <telethon.tl.custom.inlineresults.InlineResults>`, which,
|
||||||
|
|
|
@ -282,11 +282,12 @@ class AuthMethods(MessageParseMethods, UserMethods):
|
||||||
|
|
||||||
password (`str`):
|
password (`str`):
|
||||||
2FA password, should be used if a previous call raised
|
2FA password, should be used if a previous call raised
|
||||||
SessionPasswordNeededError.
|
``SessionPasswordNeededError``.
|
||||||
|
|
||||||
bot_token (`str`):
|
bot_token (`str`):
|
||||||
Used to sign in as a bot. Not all requests will be available.
|
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`):
|
phone (`str` | `int`):
|
||||||
By default, the library remembers the phone passed to
|
By default, the library remembers the phone passed to
|
||||||
|
|
|
@ -15,7 +15,7 @@ if typing.TYPE_CHECKING:
|
||||||
|
|
||||||
class _DialogsIter(RequestIter):
|
class _DialogsIter(RequestIter):
|
||||||
async def _init(
|
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(
|
self.request = functions.messages.GetDialogsRequest(
|
||||||
offset_date=offset_date,
|
offset_date=offset_date,
|
||||||
|
@ -23,6 +23,7 @@ class _DialogsIter(RequestIter):
|
||||||
offset_peer=offset_peer,
|
offset_peer=offset_peer,
|
||||||
limit=1,
|
limit=1,
|
||||||
hash=0,
|
hash=0,
|
||||||
|
exclude_pinned=ignore_pinned,
|
||||||
folder_id=folder
|
folder_id=folder
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,15 +77,19 @@ class _DialogsIter(RequestIter):
|
||||||
# we didn't get a DialogsSlice which means we got all.
|
# we didn't get a DialogsSlice which means we got all.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Don't set `request.offset_id` to the last message ID.
|
# We can't use `messages[-1]` as the offset ID / date.
|
||||||
# Why? It seems to cause plenty of dialogs to be skipped.
|
# Why? Because pinned dialogs will mess with the order
|
||||||
#
|
# in this list. Instead, we find the last dialog which
|
||||||
# By leaving it to 0 after the first iteration, even if
|
# has a message, and use it as an offset.
|
||||||
# the user originally passed another ID, we ensure that
|
last_message = next((
|
||||||
# it will work correctly.
|
messages[d.top_message]
|
||||||
self.request.offset_id = 0
|
for d in reversed(r.dialogs)
|
||||||
|
if d.top_message in messages
|
||||||
|
), None)
|
||||||
|
|
||||||
self.request.exclude_pinned = True
|
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 =\
|
self.request.offset_peer =\
|
||||||
entities[utils.get_peer_id(r.dialogs[-1].peer)]
|
entities[utils.get_peer_id(r.dialogs[-1].peer)]
|
||||||
|
|
||||||
|
@ -110,6 +115,7 @@ class DialogMethods(UserMethods):
|
||||||
offset_date: 'hints.DateLike' = None,
|
offset_date: 'hints.DateLike' = None,
|
||||||
offset_id: int = 0,
|
offset_id: int = 0,
|
||||||
offset_peer: 'hints.EntityLike' = types.InputPeerEmpty(),
|
offset_peer: 'hints.EntityLike' = types.InputPeerEmpty(),
|
||||||
|
ignore_pinned: bool = False,
|
||||||
ignore_migrated: bool = False,
|
ignore_migrated: bool = False,
|
||||||
folder: int = None,
|
folder: int = None,
|
||||||
archived: bool = None
|
archived: bool = None
|
||||||
|
@ -134,11 +140,15 @@ class DialogMethods(UserMethods):
|
||||||
offset_peer (:tl:`InputPeer`, optional):
|
offset_peer (:tl:`InputPeer`, optional):
|
||||||
The peer to be used as an offset.
|
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):
|
ignore_migrated (`bool`, optional):
|
||||||
Whether :tl:`Chat` that have ``migrated_to`` a :tl:`Channel`
|
Whether :tl:`Chat` that have ``migrated_to`` a :tl:`Channel`
|
||||||
should be included or not. By default all the chats in your
|
should be included or not. By default all the chats in your
|
||||||
dialogs are returned, but setting this to ``True`` will hide
|
dialogs are returned, but setting this to ``True`` will ignore
|
||||||
them in the same way official applications do.
|
(i.e. skip) them in the same way official applications do.
|
||||||
|
|
||||||
folder (`int`, optional):
|
folder (`int`, optional):
|
||||||
The folder from which the dialogs should be retrieved.
|
The folder from which the dialogs should be retrieved.
|
||||||
|
@ -178,6 +188,7 @@ class DialogMethods(UserMethods):
|
||||||
offset_date=offset_date,
|
offset_date=offset_date,
|
||||||
offset_id=offset_id,
|
offset_id=offset_id,
|
||||||
offset_peer=offset_peer,
|
offset_peer=offset_peer,
|
||||||
|
ignore_pinned=ignore_pinned,
|
||||||
ignore_migrated=ignore_migrated,
|
ignore_migrated=ignore_migrated,
|
||||||
folder=folder
|
folder=folder
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
@ -78,9 +79,9 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods):
|
||||||
async def send_file(
|
async def send_file(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
entity: 'hints.EntityLike',
|
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,
|
force_document: bool = False,
|
||||||
progress_callback: 'hints.ProgressCallback' = None,
|
progress_callback: 'hints.ProgressCallback' = None,
|
||||||
reply_to: 'hints.MessageIDLike' = 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
|
# First check if the user passed an iterable, in which case
|
||||||
# we may want to send as an album if all are photo files.
|
# we may want to send as an album if all are photo files.
|
||||||
if utils.is_list_like(file):
|
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
|
# TODO Fix progress_callback
|
||||||
images = []
|
images = []
|
||||||
if force_document:
|
if force_document:
|
||||||
documents = file
|
documents = file
|
||||||
else:
|
else:
|
||||||
documents = []
|
documents = []
|
||||||
for x in file:
|
for doc, cap in itertools.zip_longest(file, captions):
|
||||||
if utils.is_image(x):
|
if utils.is_image(doc):
|
||||||
images.append(x)
|
images.append(doc)
|
||||||
|
image_captions.append(cap)
|
||||||
else:
|
else:
|
||||||
documents.append(x)
|
documents.append(doc)
|
||||||
|
document_captions.append(cap)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
while images:
|
while images:
|
||||||
result += await self._send_album(
|
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,
|
progress_callback=progress_callback, reply_to=reply_to,
|
||||||
parse_mode=parse_mode, silent=silent
|
parse_mode=parse_mode, silent=silent
|
||||||
)
|
)
|
||||||
images = images[10:]
|
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(
|
result.append(await self.send_file(
|
||||||
entity, x,
|
entity, doc,
|
||||||
caption=caption, force_document=force_document,
|
caption=cap, force_document=force_document,
|
||||||
progress_callback=progress_callback, reply_to=reply_to,
|
progress_callback=progress_callback, reply_to=reply_to,
|
||||||
attributes=attributes, thumb=thumb, voice_note=voice_note,
|
attributes=attributes, thumb=thumb, voice_note=voice_note,
|
||||||
video_note=video_note, buttons=buttons, silent=silent,
|
video_note=video_note, buttons=buttons, silent=silent,
|
||||||
|
|
|
@ -3,49 +3,62 @@ Helper module around the system's libssl library if available for IGE mode.
|
||||||
"""
|
"""
|
||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
|
try:
|
||||||
|
import ctypes.macholib.dyld
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
__log__ = logging.getLogger(__name__)
|
__log__ = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
lib = ctypes.util.find_library('ssl')
|
def _find_ssl_lib():
|
||||||
|
lib = ctypes.util.find_library('ssl')
|
||||||
# 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):
|
|
||||||
paths = [
|
|
||||||
os.path.expanduser("~/lib"),
|
|
||||||
"/usr/local/lib",
|
|
||||||
"/lib",
|
|
||||||
"/usr/lib",
|
|
||||||
]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not lib:
|
if not lib:
|
||||||
raise OSError('no library called "ssl" found')
|
raise OSError('no library called "ssl" found')
|
||||||
|
|
||||||
_libssl = ctypes.cdll.LoadLibrary(lib)
|
# 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.
|
||||||
|
paths = ctypes.macholib.dyld.DEFAULT_LIBRARY_FALLBACK
|
||||||
|
except AttributeError:
|
||||||
|
paths = [
|
||||||
|
os.path.expanduser("~/lib"),
|
||||||
|
"/usr/local/lib",
|
||||||
|
"/lib",
|
||||||
|
"/usr/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
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))
|
||||||
|
return ctypes.cdll.LoadLibrary(lib)
|
||||||
|
else:
|
||||||
|
raise OSError('no absolute path for "%s" and cannot load by name' % lib)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
_libssl = _find_ssl_lib()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# See https://github.com/LonamiWebs/Telethon/issues/1167
|
# See https://github.com/LonamiWebs/Telethon/issues/1167
|
||||||
# Sometimes `find_library` returns improper filenames.
|
# 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
|
_libssl = None
|
||||||
|
|
||||||
if not _libssl:
|
if not _libssl:
|
||||||
|
|
|
@ -10,7 +10,8 @@ from ..tl.custom.sendergetter import SenderGetter
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class CallbackQuery(EventBuilder):
|
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
|
Note that the `chats` parameter will **not** work with normal
|
||||||
IDs or peers if the clicked inline button comes from a "via bot"
|
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
|
@name_inner_event
|
||||||
class ChatAction(EventBuilder):
|
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
|
@classmethod
|
||||||
def build(cls, update):
|
def build(cls, update):
|
||||||
|
|
|
@ -12,7 +12,8 @@ from ..tl.custom.sendergetter import SenderGetter
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class InlineQuery(EventBuilder):
|
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:
|
Args:
|
||||||
users (`entity`, optional):
|
users (`entity`, optional):
|
||||||
|
|
|
@ -5,7 +5,9 @@ from ..tl import types
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class MessageDeleted(EventBuilder):
|
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::
|
.. important::
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,9 @@ from ..tl import types
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class MessageEdited(NewMessage):
|
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::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from ..tl import types
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class MessageRead(EventBuilder):
|
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:
|
Args:
|
||||||
inbox (`bool`, optional):
|
inbox (`bool`, optional):
|
||||||
|
|
|
@ -8,7 +8,7 @@ from ..tl import types
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class NewMessage(EventBuilder):
|
class NewMessage(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Represents a new message event builder.
|
Occurs whenever a new text message or a message with media arrives.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
incoming (`bool`, optional):
|
incoming (`bool`, optional):
|
||||||
|
|
|
@ -4,7 +4,9 @@ from .. import utils
|
||||||
|
|
||||||
class Raw(EventBuilder):
|
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:
|
Args:
|
||||||
types (`list` | `tuple` | `type`, optional):
|
types (`list` | `tuple` | `type`, optional):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from ..tl.custom.sendergetter import SenderGetter
|
||||||
@name_inner_event
|
@name_inner_event
|
||||||
class UserUpdate(EventBuilder):
|
class UserUpdate(EventBuilder):
|
||||||
"""
|
"""
|
||||||
Represents a user update (gone online, offline, joined Telegram).
|
Occurs whenever a user goes online, starts typing, etc.
|
||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, update):
|
def build(cls, update):
|
||||||
|
@ -32,7 +32,8 @@ class UserUpdate(EventBuilder):
|
||||||
|
|
||||||
class Event(EventCommon, SenderGetter):
|
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:
|
Members:
|
||||||
online (`bool`, optional):
|
online (`bool`, optional):
|
||||||
|
|
|
@ -544,6 +544,12 @@ class MTProtoSender:
|
||||||
await self._process_message(message)
|
await self._process_message(message)
|
||||||
|
|
||||||
async def _handle_update(self, 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__)
|
self._log.debug('Handling update %s', message.obj.__class__.__name__)
|
||||||
if self._update_callback:
|
if self._update_callback:
|
||||||
self._update_callback(message.obj)
|
self._update_callback(message.obj)
|
||||||
|
|
|
@ -285,11 +285,12 @@ class Conversation(ChatGetter):
|
||||||
return await self._get_result(future, start_time, timeout, self._custom, counter)
|
return await self._get_result(future, start_time, timeout, self._custom, counter)
|
||||||
|
|
||||||
async def _check_custom(self, built):
|
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)
|
ev_type = type(ev)
|
||||||
inst = built[ev_type]
|
inst = built[ev_type]
|
||||||
if inst and ev.filter(inst):
|
if inst and ev.filter(inst):
|
||||||
fut.set_result(inst)
|
fut.set_result(inst)
|
||||||
|
del self._custom[key]
|
||||||
|
|
||||||
def _on_new_message(self, response):
|
def _on_new_message(self, response):
|
||||||
response = response.message
|
response = response.message
|
||||||
|
@ -302,22 +303,28 @@ class Conversation(ChatGetter):
|
||||||
|
|
||||||
self._incoming.append(response)
|
self._incoming.append(response)
|
||||||
|
|
||||||
# Note: we don't remove from pending here, that's done on get result
|
# Most of the time, these dictionaries will contain just one item
|
||||||
for msg_id, future in self._pending_responses.items():
|
# 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)
|
self._response_indices[msg_id] = len(self._incoming)
|
||||||
future.set_result(response)
|
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:
|
if msg_id == response.reply_to_msg_id:
|
||||||
self._reply_indices[msg_id] = len(self._incoming)
|
self._reply_indices[msg_id] = len(self._incoming)
|
||||||
future.set_result(response)
|
future.set_result(response)
|
||||||
|
del self._pending_replies[msg_id]
|
||||||
|
|
||||||
def _on_edit(self, message):
|
def _on_edit(self, message):
|
||||||
message = message.message
|
message = message.message
|
||||||
if message.chat_id != self.chat_id or message.out:
|
if message.chat_id != self.chat_id or message.out:
|
||||||
return
|
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:
|
if msg_id < message.id:
|
||||||
edit_ts = message.edit_date.timestamp()
|
edit_ts = message.edit_date.timestamp()
|
||||||
|
|
||||||
|
@ -330,6 +337,7 @@ class Conversation(ChatGetter):
|
||||||
self._edit_dates[msg_id] = message.edit_date.timestamp()
|
self._edit_dates[msg_id] = message.edit_date.timestamp()
|
||||||
|
|
||||||
future.set_result(message)
|
future.set_result(message)
|
||||||
|
del self._pending_edits[msg_id]
|
||||||
|
|
||||||
def _on_read(self, event):
|
def _on_read(self, event):
|
||||||
if event.chat_id != self.chat_id or event.inbox:
|
if event.chat_id != self.chat_id or event.inbox:
|
||||||
|
@ -338,10 +346,11 @@ class Conversation(ChatGetter):
|
||||||
self._last_read = event.max_id
|
self._last_read = event.max_id
|
||||||
|
|
||||||
remove_reads = []
|
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:
|
if msg_id >= self._last_read:
|
||||||
remove_reads.append(msg_id)
|
remove_reads.append(msg_id)
|
||||||
pending.set_result(True)
|
pending.set_result(True)
|
||||||
|
del self._pending_reads[msg_id]
|
||||||
|
|
||||||
for to_remove in remove_reads:
|
for to_remove in remove_reads:
|
||||||
del self._pending_reads[to_remove]
|
del self._pending_reads[to_remove]
|
||||||
|
@ -365,14 +374,16 @@ class Conversation(ChatGetter):
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
due = min(due, start_time + timeout)
|
due = min(due, start_time + timeout)
|
||||||
|
|
||||||
try:
|
# NOTE: We can't try/finally to pop from pending here because
|
||||||
return await asyncio.wait_for(
|
# the event loop needs to get back to us, but it might
|
||||||
future,
|
# dispatch another update before, and in that case a
|
||||||
timeout=None if due == float('inf') else due - time.time(),
|
# response could be set twice. So responses must be
|
||||||
loop=self._client.loop
|
# cleared when their futures are set to a result.
|
||||||
)
|
return await asyncio.wait_for(
|
||||||
finally:
|
future,
|
||||||
del pending[target_id]
|
timeout=None if due == float('inf') else due - time.time(),
|
||||||
|
loop=self._client.loop
|
||||||
|
)
|
||||||
|
|
||||||
def _cancel_all(self, exception=None):
|
def _cancel_all(self, exception=None):
|
||||||
self._cancelled = True
|
self._cancelled = True
|
||||||
|
|
|
@ -855,6 +855,16 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
||||||
else:
|
else:
|
||||||
return await self._buttons[i][j].click()
|
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):
|
async def pin(self, *, notify=False):
|
||||||
"""
|
"""
|
||||||
Pins the message. Shorthand for
|
Pins the message. Shorthand for
|
||||||
|
|
|
@ -106,9 +106,13 @@ def get_extension(media):
|
||||||
"""Gets the corresponding extension for any Telegram media."""
|
"""Gets the corresponding extension for any Telegram media."""
|
||||||
|
|
||||||
# Photos are always compressed as .jpg by Telegram
|
# Photos are always compressed as .jpg by Telegram
|
||||||
if isinstance(media, (types.UserProfilePhoto,
|
try:
|
||||||
types.ChatPhoto, types.MessageMediaPhoto)):
|
get_input_photo(media)
|
||||||
return '.jpg'
|
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
|
# Documents will come with a mime type
|
||||||
if isinstance(media, types.MessageMediaDocument):
|
if isinstance(media, types.MessageMediaDocument):
|
||||||
|
@ -290,7 +294,10 @@ def get_input_photo(photo):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
_raise_cast_fail(photo, 'InputPhoto')
|
_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
|
photo = photo.photo
|
||||||
|
|
||||||
if isinstance(photo, types.Photo):
|
if isinstance(photo, types.Photo):
|
||||||
|
@ -302,6 +309,7 @@ def get_input_photo(photo):
|
||||||
|
|
||||||
if isinstance(photo, types.messages.ChatFull):
|
if isinstance(photo, types.messages.ChatFull):
|
||||||
photo = photo.full_chat
|
photo = photo.full_chat
|
||||||
|
|
||||||
if isinstance(photo, types.ChannelFull):
|
if isinstance(photo, types.ChannelFull):
|
||||||
return get_input_photo(photo.chat_photo)
|
return get_input_photo(photo.chat_photo)
|
||||||
elif isinstance(photo, types.UserFull):
|
elif isinstance(photo, types.UserFull):
|
||||||
|
@ -678,7 +686,8 @@ def _get_extension(file):
|
||||||
# Note: ``file.name`` works for :tl:`InputFile` and some `IOBase`
|
# Note: ``file.name`` works for :tl:`InputFile` and some `IOBase`
|
||||||
return _get_extension(file.name)
|
return _get_extension(file.name)
|
||||||
else:
|
else:
|
||||||
return ''
|
# Maybe it's a Telegram media
|
||||||
|
return get_extension(file)
|
||||||
|
|
||||||
|
|
||||||
def is_image(file):
|
def is_image(file):
|
||||||
|
|
|
@ -171,8 +171,22 @@ function updateSearch(event) {
|
||||||
var foundTypes = getSearchArray(types, typesu, query);
|
var foundTypes = getSearchArray(types, typesu, query);
|
||||||
var foundConstructors = getSearchArray(constructors, constructorsu, 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 (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];
|
window.location = foundRequests[1][0];
|
||||||
} else if (typesDetails.open && foundTypes[1].length) {
|
} else if (typesDetails.open && foundTypes[1].length) {
|
||||||
window.location = foundTypes[1][0];
|
window.location = foundTypes[1][0];
|
||||||
|
@ -187,18 +201,6 @@ function updateSearch(event) {
|
||||||
buildList(constructorsCount, constructorsList, foundConstructors);
|
buildList(constructorsCount, constructorsList, foundConstructors);
|
||||||
|
|
||||||
// Now look for exact matches
|
// 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) {
|
if (destination.length == 0) {
|
||||||
exactMatch.style.display = "none";
|
exactMatch.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user