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.
|
||||
|
||||
.. _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,49 +3,62 @@ 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')
|
||||
|
||||
# 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:
|
||||
def _find_ssl_lib():
|
||||
lib = ctypes.util.find_library('ssl')
|
||||
if not lib:
|
||||
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:
|
||||
# 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:
|
||||
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]
|
||||
# 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
|
||||
)
|
||||
|
||||
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,9 +106,13 @@ 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
|
||||
if isinstance(media, types.MessageMediaDocument):
|
||||
|
@ -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