mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-12-01 22:03:46 +03:00
988 lines
44 KiB
ReStructuredText
988 lines
44 KiB
ReStructuredText
=========================
|
|
Version 2 Migration Guide
|
|
=========================
|
|
|
|
Version 2 represents the second major version change, breaking compatibility
|
|
with old code beyond the usual raw API changes in order to clean up a lot of
|
|
the technical debt that has grown on the project.
|
|
|
|
This document documents all the things you should be aware of when migrating from Telethon version
|
|
1.x to 2.0 onwards. It is sorted roughly from the "likely most impactful changes" to "there's a
|
|
good chance you were not relying on this to begin with".
|
|
|
|
**Please read this document in full before upgrading your code to Telethon 2.0.**
|
|
|
|
|
|
Python 3.5 is no longer supported
|
|
---------------------------------
|
|
|
|
The library will no longer attempt to support Python 3.5. The minimum version is now Python 3.7.
|
|
|
|
This also means workarounds for 3.6 and below have been dropped.
|
|
|
|
|
|
User, chat and channel identifiers are now 64-bit numbers
|
|
---------------------------------------------------------
|
|
|
|
`Layer 133 <https://diff.telethon.dev/?from=132&to=133>`__ changed *a lot* of identifiers from
|
|
``int`` to ``long``, meaning they will no longer fit in 32 bits, and instead require 64 bits.
|
|
|
|
If you were storing these identifiers somewhere size did matter (for example, a database), you
|
|
will need to migrate that to support the new size requirement of 8 bytes.
|
|
|
|
For the full list of types changed, please review the above link.
|
|
|
|
|
|
Peer IDs, including chat_id and sender_id, no longer follow bot API conventions
|
|
-------------------------------------------------------------------------------
|
|
|
|
Both the ``utils.get_peer_id`` and ``client.get_peer_id`` methods no longer have an ``add_mark``
|
|
parameter. Both will always return the original ID as given by Telegram. This should lead to less
|
|
confusion. However, it also means that an integer ID on its own no longer embeds the information
|
|
about the type (did it belong to a user, chat, or channel?), so ``utils.get_peer`` can no longer
|
|
guess the type from just a number.
|
|
|
|
Because it's not possible to know what other changes Telegram will do with identifiers, it's
|
|
probably best to get used to transparently storing whatever value they send along with the type
|
|
separatedly.
|
|
|
|
As far as I can tell, user, chat and channel identifiers are globally unique, meaning a channel
|
|
and a user cannot share the same identifier. The library currently makes this assumption. However,
|
|
this is merely an observation (I have never heard of such a collision exist), and Telegram could
|
|
change at any time. If you want to be on the safe side, you're encouraged to save a pair of type
|
|
and identifier, rather than just the number.
|
|
|
|
// TODO we DEFINITELY need to provide a way to "upgrade" old ids
|
|
// TODO and storing type+number by hand is a pain, provide better alternative
|
|
|
|
|
|
Synchronous compatibility mode has been removed
|
|
-----------------------------------------------
|
|
|
|
The "sync hack" (which kicked in as soon as anything from ``telethon.sync`` was imported) has been
|
|
removed. This implies:
|
|
|
|
* The ``telethon.sync`` module is gone.
|
|
* Synchronous context-managers (``with`` as opposed to ``async with``) are no longer supported.
|
|
Most notably, you can no longer do ``with client``. It must be ``async with client`` now.
|
|
* The "smart" behaviour of the following methods has been removed and now they no longer work in
|
|
a synchronous context when the ``asyncio`` event loop was not running. This means they now need
|
|
to be used with ``await`` (or, alternatively, manually used with ``loop.run_until_complete``):
|
|
* ``start``
|
|
* ``disconnect``
|
|
* ``run_until_disconnected``
|
|
|
|
// TODO provide standalone alternative for this?
|
|
|
|
|
|
Overhaul of events and updates
|
|
------------------------------
|
|
|
|
Updates produced by the client are now also processed by your event handlers.
|
|
Before, if you had some code listening for new outgoing messages, only messages you sent with
|
|
another client, such as from Telegram Desktop, would be processed. Now, if your own code uses
|
|
``client.send_message``, you will also receive the new message event. Be careful, as this can
|
|
easily lead to "loops" (a new outgoing message can trigger ``client.send_message``, which
|
|
triggers a new outgoing message and the cycle repeats)!
|
|
|
|
There are no longer "event builders" and "event" types. Now there are only events, and you
|
|
register the type of events you want, not an instance. Because of this, the way filters are
|
|
specified have also changed:
|
|
|
|
.. code-block:: python
|
|
|
|
# OLD
|
|
@client.on(events.NewMessage(chats=...))
|
|
async def handler(event):
|
|
pass
|
|
|
|
# NEW
|
|
@client.on(events.NewMessage, chats=...)
|
|
async def handler(event): # ^^ ^
|
|
pass
|
|
|
|
This also means filters are unified, although not all filters have an effect on all events types.
|
|
Type hinting is now done through ``events.NewMessage`` and not ``events.NewMessage.Event``.
|
|
|
|
The filter rework also enables more features. For example, you can now mutate a ``chats`` filter
|
|
to add or remove a chat that needs to be received by a handler, rather than having to remove and
|
|
re-add the event handler.
|
|
|
|
The ``from_users`` filter has been renamed to ``senders``.
|
|
|
|
The ``inbox`` filter for ``events.MessageRead`` has been removed, in favour of ``outgoing`` and
|
|
``incoming``.
|
|
|
|
``events.register``, ``events.unregister`` and ``events.is_handler`` have been removed. There is
|
|
no longer anything special about methods which are handlers, and they are no longer monkey-patched.
|
|
Because pre-defining the event type to handle without a client was useful, you can now instead use
|
|
the following syntax:
|
|
|
|
.. code-block:: python
|
|
|
|
# OLD
|
|
@events.register(events.NewMessage)
|
|
async def handler(event):
|
|
pass
|
|
|
|
# NEW
|
|
async def handler(event: events.NewMessage):
|
|
pass # ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
As a bonus, you only need to type-hint once, and both your IDE and Telethon will understand what
|
|
you meant. This is similar to Python's ``@dataclass`` which uses type hints.
|
|
|
|
// TODO document filter creation and usage, showcase how to mutate them
|
|
|
|
|
|
Complete overhaul of session files
|
|
----------------------------------
|
|
|
|
If you were using third-party libraries to deal with sessions, you will need to wait for those to
|
|
be updated. The library will automatically upgrade the SQLite session files to the new version,
|
|
and the ``StringSession`` remains backward-compatible. The sessions can now be async.
|
|
|
|
In case you were relying on the tables used by SQLite (even though these should have been, and
|
|
will still need to be, treated as an implementation detail), here are the changes:
|
|
|
|
* The ``sessions`` table is now correctly split into ``datacenter`` and ``session``.
|
|
``datacenter`` contains information about a Telegram datacenter, along with its corresponding
|
|
authorization key, and ``session`` contains information about the update state and user.
|
|
* The ``entities`` table is now called ``entity`` and stores the ``type`` separatedly.
|
|
* The ``update_state`` table is now split into ``session`` and ``channel``, which can contain
|
|
a per-channel ``pts``.
|
|
|
|
Because **the new version does not cache usernames, phone numbers and display names**, using these
|
|
in method calls is now quite expensive. You *should* migrate your code to do the Right Thing and
|
|
start using identifiers rather than usernames, phone numbers or invite links. This is both simpler
|
|
and more reliable, because while a user identifier won't change, their username could.
|
|
|
|
You can use the following snippet to make a JSON backup (alternatively, you could just copy the
|
|
``.session`` file and keep it around) in case you want to preserve the cached usernames:
|
|
|
|
.. code-block:: python
|
|
|
|
import sqlite, json
|
|
with sqlite3.connect('your.session') as conn, open('entities.json', 'w', encoding='utf-8') as fp:
|
|
json.dump([
|
|
{'id': id, 'hash': hash, 'username': username, 'phone': phone, 'name': name, 'date': date}
|
|
for (id, hash, username, phone, name, date)
|
|
in conn.execute('select id, hash, username, phone, name, date from entities')
|
|
], fp)
|
|
|
|
The following public methods or properties have also been removed from ``SQLiteSession`` because
|
|
they no longer make sense:
|
|
|
|
* ``list_sessions``. You can ``glob.glob('*.session')`` instead.
|
|
* ``clone``.
|
|
|
|
And the following, which were inherited from ``MemorySession``:
|
|
|
|
* ``delete``. You can ``os.remove`` the file instead (preferably after ``client.log_out()``).
|
|
``client.log_out()`` also no longer deletes the session file (it can't as there's no method).
|
|
* ``set_dc``.
|
|
* ``dc_id``.
|
|
* ``server_address``.
|
|
* ``port``.
|
|
* ``auth_key``.
|
|
* ``takeout_id``.
|
|
* ``get_update_state``.
|
|
* ``set_update_state``.
|
|
* ``process_entities``.
|
|
* ``get_entity_rows_by_phone``.
|
|
* ``get_entity_rows_by_username``.
|
|
* ``get_entity_rows_by_name``.
|
|
* ``get_entity_rows_by_id``.
|
|
* ``get_input_entity``.
|
|
* ``cache_file``.
|
|
* ``get_file``.
|
|
|
|
You also can no longer set ``client.session.save_entities = False``. The entities must be saved
|
|
for the library to work properly. If you still don't want it, you should subclass the session and
|
|
override the methods to do nothing.
|
|
|
|
|
|
Complete overhaul of errors
|
|
---------------------------
|
|
|
|
The following error name have changed to follow a better naming convention (clearer acronyms):
|
|
|
|
* ``RPCError`` is now ``RpcError``.
|
|
* ``InvalidDCError`` is now ``InvalidDcError`` (lowercase ``c``).
|
|
|
|
The base errors no longer have a ``.message`` field at the class-level. Instead, it is now an
|
|
attribute at the instance level (meaning you cannot do ``BadRequestError.message``, it must be
|
|
``bad_request_err.message`` where ``isinstance(bad_request_err, BadRequestError)``).
|
|
|
|
The ``.message`` will gain its value at the time the error is constructed, rather than being
|
|
known beforehand.
|
|
|
|
The parameter order for ``RpcError`` and all its subclasses are now ``(code, message, request)``,
|
|
as opposed to ``(message, request, code)``.
|
|
|
|
Because Telegram errors can be added at any time, the library no longer generate a fixed set of
|
|
them. This means you can no longer use ``dir`` to get a full list of them. Instead, the errors
|
|
are automatically generated depending on the name you use for the error, with the following rules:
|
|
|
|
* Numbers are removed from the name. The Telegram error ``FLOOD_WAIT_42`` is transformed into
|
|
``FLOOD_WAIT_``.
|
|
* Underscores are removed from the name. ``FLOOD_WAIT_`` becomes ``FLOODWAIT``.
|
|
* Everything is lowercased. ``FLOODWAIT`` turns into ``floodwait``.
|
|
* While the name ends with ``error``, this suffix is removed.
|
|
|
|
The only exception to this rule is ``2FA_CONFIRM_WAIT_0``, which is transformed as
|
|
``twofaconfirmwait`` (read as ``TwoFaConfirmWait``).
|
|
|
|
What all this means is that, if Telegram raises a ``FLOOD_WAIT_42``, you can write the following:
|
|
|
|
.. code-block:: python
|
|
|
|
from telethon.errors import FloodWaitError
|
|
|
|
try:
|
|
await client.send_message(chat, message)
|
|
except FloodWaitError as e:
|
|
print(f'Flood! wait for {e.seconds} seconds')
|
|
|
|
Essentially, old code will keep working, but now you have the freedom to define even yet-to-be
|
|
discovered errors. This makes use of `PEP 562 <https://www.python.org/dev/peps/pep-0562/>`__ on
|
|
Python 3.7 and above and a more-hacky approach below (which your IDE may not love).
|
|
|
|
Given the above rules, you could also write ``except errors.FLOOD_WAIT`` if you prefer to match
|
|
Telegram's naming conventions. We recommend Camel-Case naming with the "Error" suffix, but that's
|
|
up to you.
|
|
|
|
All errors will include a list of ``.values`` (the extracted number) and ``.value`` (the first
|
|
number extracted, or ``None`` if ``values`` is empty). In addition to that, certain errors have
|
|
a more-recognizable alias (such as ``FloodWait`` which has ``.seconds`` for its ``.value``).
|
|
|
|
The ``telethon.errors`` module continues to provide certain predefined ``RpcError`` to match on
|
|
the *code* of the error and not its message (for instance, match all errors with code 403 with
|
|
``ForbiddenError``). Note that a certain error message can appear with different codes too, this
|
|
is decided by Telegram.
|
|
|
|
The ``telethon.errors`` module continues to provide custom errors used by the library such as
|
|
``TypeNotFoundError``.
|
|
|
|
// TODO keep RPCError around? eh idk how much it's used
|
|
// TODO should RpcError subclass ValueError? technically the values used in the request somehow were wrong…
|
|
// TODO provide a way to see which errors are known in the docs or at tl.telethon.dev
|
|
|
|
|
|
Changes to the default parse mode
|
|
---------------------------------
|
|
|
|
The default markdown parse mode now conforms to the commonmark specification.
|
|
|
|
The old markdown parser (which was used as the default ``client.parse_mode``) used to emulate
|
|
Telegram Desktop's behaviour. Now `<markdown-it-py https://github.com/executablebooks/markdown-it-py>`__
|
|
is used instead, which fixes certain parsing bugs but also means the formatting will be different.
|
|
|
|
Most notably, ``__`` will now make text bold. If you want the old behaviour, use a single
|
|
underscore instead (such as ``_``). You can also use a single asterisk (``*``) for italics.
|
|
Because now there's proper parsing, you also gain:
|
|
|
|
* Headings (``# text``) will now be underlined.
|
|
* Certain HTML tags will now also be recognized in markdown (including ``<u>`` for underlining text).
|
|
* Line breaks behave properly now. For a single-line break, end your line with ``\\``.
|
|
* Inline links should no longer behave in a strange manner.
|
|
* Pre-blocks can now have a language. Official clients don't syntax highlight code yet, though.
|
|
|
|
Furthermore, the parse mode is no longer client-dependant. It is now configured through ``Message``.
|
|
|
|
// TODO provide a way to get back the old behaviour?
|
|
|
|
|
|
The "iter" variant of the client methods have been removed
|
|
----------------------------------------------------------
|
|
|
|
Instead, you can now use the result of the ``get_*`` variant. For instance, where before you had:
|
|
|
|
.. code-block:: python
|
|
|
|
async for message in client.iter_messages(...):
|
|
pass
|
|
|
|
You would now do:
|
|
|
|
.. code-block:: python
|
|
|
|
async for message in client.get_messages(...):
|
|
pass # ^^^ now it's get, not iter
|
|
|
|
You can still use ``await`` on the ``get_`` methods to retrieve the list.
|
|
|
|
The removed methods are:
|
|
|
|
* iter_messages
|
|
* iter_dialogs
|
|
* iter_participants
|
|
* iter_admin_log
|
|
* iter_profile_photos
|
|
* iter_drafts
|
|
|
|
The only exception to this rule is ``iter_download``.
|
|
|
|
Additionally, when using ``await``, if the method was called with a limit of 1 (either through
|
|
setting just one value to fetch, or setting the limit to one), either ``None`` or a single item
|
|
(outside of a ``list``) will be returned. This used to be the case only for ``get_messages``,
|
|
but now all methods behave in the same way for consistency.
|
|
|
|
When using ``async for``, the default limit will be ``None``, meaning all items will be fetched.
|
|
When using ``await``, the default limit will be ``1``, meaning the latest item will be fetched.
|
|
If you want to use ``await`` but still get a list, use the ``.collect()`` method to collect the
|
|
results into a list:
|
|
|
|
.. code-block:: python
|
|
|
|
chat = ...
|
|
|
|
# will iterate over all (default limit=None)
|
|
async for message in client.get_messages(chat):
|
|
...
|
|
|
|
# will return either a single Message or None if there is not any (limit=1)
|
|
message = await client.get_messages(chat)
|
|
|
|
# will collect all messages into a list (default limit=None). will also take long!
|
|
all_messages = await client.get_messages(chat).collect()
|
|
|
|
|
|
// TODO keep providing the old ``iter_`` versions? it doesn't really hurt, even if the recommended way changed
|
|
// TODO does the download really need to be special? get download is kind of weird though
|
|
|
|
|
|
Raw API has been renamed and is now immutable and considered private
|
|
--------------------------------------------------------------------
|
|
|
|
The subpackage holding the raw API methods has been renamed from ``tl`` to ``_tl`` in order to
|
|
signal that these are prone to change across minor version bumps (the ``y`` in version ``x.y.z``).
|
|
|
|
Because in Python "we're all adults", you *can* use this private module if you need to. However,
|
|
you *are* also acknowledging that this is a private module prone to change (and indeed, it will
|
|
change on layer upgrades across minor version bumps).
|
|
|
|
The ``Request`` suffix has been removed from the classes inside ``tl.functions``.
|
|
|
|
The ``tl.types`` is now simply ``_tl``, and the ``tl.functions`` is now ``_tl.fn``.
|
|
|
|
Both the raw API types and functions are now immutable. This can enable optimizations in the
|
|
future, such as greatly reducing the number of intermediate objects created (something worth
|
|
doing for deeply-nested objects).
|
|
|
|
Some examples:
|
|
|
|
.. code-block:: python
|
|
|
|
# Before
|
|
from telethon.tl import types, functions
|
|
|
|
await client(functions.messages.SendMessageRequest(...))
|
|
message: types.Message = ...
|
|
|
|
# After
|
|
from telethon import _tl
|
|
await client(_tl.fn.messages.SendMessage(...))
|
|
message: _tl.Message
|
|
|
|
This serves multiple goals:
|
|
|
|
* It removes redundant parts from the names. The "recommended" way of using the raw API is through
|
|
the subpackage namespace, which already contains a mention to "functions" in it. In addition,
|
|
some requests were awkward, such as ``SendCustomRequestRequest``.
|
|
* It makes it easier to search for code that is using the raw API, so that you can quickly
|
|
identify which parts are making use of it.
|
|
* The name is shorter, but remains recognizable.
|
|
|
|
Because *a lot* of these objects are created, they now define ``__slots__``. This means you can
|
|
no longer monkey-patch them to add new attributes at runtime. You have to create a subclass if you
|
|
want to define new attributes.
|
|
|
|
This also means that the updates from ``events.Raw`` **no longer have** ``update._entities``.
|
|
|
|
``tlobject.to_dict()`` has changed and is now generated dynamically based on the ``__slots__`.
|
|
This may incur a small performance hit (but you shouldn't really be using ``.to_dict()`` when
|
|
you can just use attribute access and ``getattr``). In general, this should handle ill-defined
|
|
objects more gracefully (for instance, those where you're using a ``tuple`` and not a ``list``
|
|
or using a list somewhere it shouldn't be), and have no other observable effects. As an extra
|
|
benefit, this slightly cuts down on the amount of bloat.
|
|
|
|
In ``tlobject.to_dict()``, the special ``_`` key is now also contains the module (so you can
|
|
actually distinguish between equally-named classes). If you want the old behaviour, use
|
|
``tlobject.__class__.__name__` instead (and add ``Request`` for functions).
|
|
|
|
Because the string representation of an object used ``tlobject.to_dict()``, it is now also
|
|
affected by these changes.
|
|
|
|
// TODO this definitely generated files mapping from the original name to this new one...
|
|
// TODO what's the alternative to update._entities? and update._client??
|
|
|
|
|
|
Many subpackages and modules are now private
|
|
--------------------------------------------
|
|
|
|
There were a lot of things which were public but should not have been. From now on, you should
|
|
only rely on things that are either publicly re-exported or defined. That is, as soon as anything
|
|
starts with an underscore (``_``) on its name, you're acknowledging that the functionality may
|
|
change even across minor version changes, and thus have your code break.
|
|
|
|
The following subpackages are now considered private:
|
|
|
|
* ``client`` is now ``_client``.
|
|
* ``crypto`` is now ``_crypto``.
|
|
* ``extensions`` is now ``_misc``.
|
|
* ``tl`` is now ``_tl``.
|
|
|
|
The following modules have been moved inside ``_misc``:
|
|
|
|
* ``entitycache.py``
|
|
* ``helpers.py``
|
|
* ``hints.py``
|
|
* ``password.py``
|
|
* ``requestiter.py``
|
|
* ``statecache.py``
|
|
* ``utils.py``
|
|
|
|
// TODO review telethon/__init__.py isn't exposing more than it should
|
|
|
|
|
|
Using the client in a context-manager no longer calls start automatically
|
|
-------------------------------------------------------------------------
|
|
|
|
The following code no longer automatically calls ``client.start()``:
|
|
|
|
.. code-block:: python
|
|
|
|
async with TelegramClient(...) as client:
|
|
...
|
|
|
|
# or
|
|
|
|
async with client:
|
|
...
|
|
|
|
|
|
This means the context-manager will only call ``client.connect()`` and ``client.disconnect()``.
|
|
The rationale for this change is that it could be strange for this to ask for the login code if
|
|
the session ever was invalid. If you want the old behaviour, you now need to be explicit:
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
async with TelegramClient(...).start() as client:
|
|
... # ++++++++
|
|
|
|
|
|
Note that you do not need to ``await`` the call to ``.start()`` if you are going to use the result
|
|
in a context-manager (but it's okay if you put the ``await``).
|
|
|
|
|
|
Changes to sending messages and files
|
|
-------------------------------------
|
|
|
|
When sending messages or files, there is no longer a parse mode. Instead, the ``markdown`` or
|
|
``html`` parameters can be used instead of the (plaintext) ``message``.
|
|
|
|
.. code-block:: python
|
|
|
|
await client.send_message(chat, 'Default formatting (_markdown_)')
|
|
await client.send_message(chat, html='Force <em>HTML</em> formatting')
|
|
await client.send_message(chat, markdown='Force **Markdown** formatting')
|
|
|
|
These 3 parameters are exclusive with each other (you can only use one). The goal here is to make
|
|
it consistent with the custom ``Message`` class, which also offers ``.markdown`` and ``.html``
|
|
properties to obtain the correctly-formatted text, regardless of the default parse mode, and to
|
|
get rid of some implicit behaviour. It's also more convenient to set just one parameter than two
|
|
(the message and the parse mode separatedly).
|
|
|
|
Although the goal is to reduce raw API exposure, ``formatting_entities`` stays, because it's the
|
|
only feasible way to manually specify them.
|
|
|
|
When sending files, you can no longer pass a list of attributes. This was a common workaround to
|
|
set video size, audio duration, and so on. Now, proper parameters are available. The goal is to
|
|
hide raw API as much as possible (which lets the library hide future breaking changes as much as
|
|
possible). One can still use raw API if really needed.
|
|
|
|
|
|
Several methods have been removed from the client
|
|
-------------------------------------------------
|
|
|
|
``client.download_file`` has been removed. Instead, ``client.download_media`` should be used.
|
|
The now-removed ``client.download_file`` method was a lower level implementation which should
|
|
have not been exposed at all.
|
|
|
|
``client.build_reply_markup`` has been removed. Manually calling this method was purely an
|
|
optimization (the buttons won't need to be transformed into a reply markup every time they're
|
|
used). This means you can just remove any calls to this method and things will continue to work.
|
|
|
|
|
|
Support for bot-API style file_id has been removed
|
|
--------------------------------------------------
|
|
|
|
They have been half-broken for a while now, so this is just making an existing reality official.
|
|
See `issue #1613 <https://github.com/LonamiWebs/Telethon/issues/1613>`__ for details.
|
|
|
|
An alternative solution to re-use files may be provided in the future. For the time being, you
|
|
should either upload the file as needed, or keep a message with the media somewhere you can
|
|
later fetch it (by storing the chat and message identifier).
|
|
|
|
Additionally, the ``custom.File.id`` property is gone (which used to provide access to this
|
|
"bot-API style" file identifier.
|
|
|
|
// TODO could probably provide an in-memory cache for uploads to temporarily reuse old InputFile.
|
|
// this should lessen the impact of the removal of this feature
|
|
|
|
|
|
Removal of several utility methods
|
|
----------------------------------
|
|
|
|
The following ``utils`` methods no longer exist or have been made private:
|
|
|
|
* ``utils.resolve_bot_file_id``. It was half-broken.
|
|
* ``utils.pack_bot_file_id``. It was half-broken.
|
|
* ``utils.resolve_invite_link``. It has been broken for a while, so this just makes its removal
|
|
official (see `issue #1723 <https://github.com/LonamiWebs/Telethon/issues/1723>`__).
|
|
* ``utils.resolve_id``. Marked IDs are no longer used thorough the library. The removal of this
|
|
method also means ``utils.get_peer`` can no longer get a ``Peer`` from just a number, as the
|
|
type is no longer embedded inside the ID.
|
|
|
|
// TODO provide the new clean utils
|
|
|
|
|
|
Changes to many friendly methods in the client
|
|
----------------------------------------------
|
|
|
|
Some of the parameters used to initialize the ``TelegramClient`` have been renamed to be clearer:
|
|
|
|
* ``timeout`` is now ``connect_timeout``.
|
|
* ``connection_retries`` is now ``connect_retries``.
|
|
* ``retry_delay`` is now ``connect_retry_delay``.
|
|
* ``raise_last_call_error`` has been removed and is now the default. This means you won't get a
|
|
``ValueError`` if an API call fails multiple times, but rather the original error.
|
|
* ``connection`` to change the connection mode has been removed for the time being.
|
|
* ``sequential_updates`` has been removed for the time being.
|
|
|
|
// TODO document new parameters too
|
|
|
|
``client.send_code_request`` no longer has ``force_sms`` (it was broken and was never reliable).
|
|
|
|
``client.send_read_acknowledge`` is now ``client.mark_read``, consistent with the method of
|
|
``Message``, being shorter and less awkward to type. The method now only supports a single
|
|
message, not a list (the list was a lie, because all messages up to the one with the highest
|
|
ID were marked as read, meaning one could not leave unread gaps). ``max_id`` is now removed,
|
|
since it has the same meaning as the message to mark as read. The method no longer can clear
|
|
mentions without marking the chat as read, but this should not be an issue in practice.
|
|
|
|
Every ``client.action`` can now be directly ``await``-ed, not just ``'cancel'``.
|
|
|
|
``client.forward_messages`` now requires a list to be specified. The intention is to make it clear
|
|
that the method forwards message\ **s** and to reduce the number of strange allowed values, which
|
|
needlessly complicate the code. If you still need to forward a single message, manually construct
|
|
a list with ``[message]`` or use ``Message.forward_to``.
|
|
|
|
``client.delete_messages`` now requires a list to be specified, with the same rationale as forward.
|
|
|
|
``client.get_me`` no longer has an ``input_peer`` parameter. The goal is to hide raw API as much
|
|
as possible. Input peers are mostly an implementation detail the library needs to deal with
|
|
Telegram's API.
|
|
|
|
Before, ``client.iter_participants`` (and ``get_participants``) would expect a type or instance
|
|
of the raw Telegram definition as a ``filter``. Now, this ``filter`` expects a string.
|
|
The supported values are:
|
|
|
|
* ``'admin'``
|
|
* ``'bot'``
|
|
* ``'kicked'``
|
|
* ``'banned'``
|
|
* ``'contact'``
|
|
|
|
If you prefer to avoid hardcoding strings, you may use ``telethon.enums.Participant``.
|
|
|
|
The size selector for ``client.download_profile_photo`` and ``client.download_media`` is now using
|
|
an enumeration:
|
|
|
|
.. code-block:: python
|
|
|
|
from telethon import enums
|
|
|
|
await client.download_profile_photo(user, thumb=enums.Size.ORIGINAL)
|
|
|
|
This new selection mode is also smart enough to pick the "next best" size if the specified one
|
|
is not available. The parameter is known as ``thumb`` and not ``size`` because documents don't
|
|
have a "size", they have thumbnails of different size. For profile photos, the thumbnail size is
|
|
also used.
|
|
|
|
// TODO maintain support for the old way of doing it?
|
|
// TODO now that there's a custom filter, filter client-side for small chats?
|
|
|
|
|
|
The custom.Message class and the way it is used has changed
|
|
-----------------------------------------------------------
|
|
|
|
It no longer inherits ``TLObject``, and rather than trying to mimick Telegram's ``Message``
|
|
constructor, it now takes two parameters: a ``TelegramClient`` instance and a ``_tl.Message``.
|
|
As a benefit, you can now more easily reconstruct instances of this type from a previously-stored
|
|
``_tl.Message`` instance.
|
|
|
|
There are no public attributes. Instead, they are now properties which forward the values into and
|
|
from the private ``_message`` field. As a benefit, the documentation will now be easier to follow.
|
|
However, you can no longer use ``del`` on these.
|
|
|
|
The ``_tl.Message.media`` attribute will no longer be ``None`` when using raw API if the media was
|
|
``messageMediaEmpty``. As a benefit, you can now actually distinguish between no media and empty
|
|
media. The ``Message.media`` property as returned by friendly methods will still be ``None`` on
|
|
empty media.
|
|
|
|
The ``telethon.tl.patched`` hack has been removed.
|
|
|
|
The message sender no longer is the channel when no sender is provided by Telegram. Telethon used
|
|
to patch this value for channels to be the same as the chat, but now it will be faithful to
|
|
Telegram's value.
|
|
|
|
|
|
Overhaul of users and chats are no longer raw API types
|
|
-------------------------------------------------------
|
|
|
|
Users and chats are no longer raw API types. The goal is to reduce the amount of raw API exposed
|
|
to the user, and to provide less confusing naming. This also means that **the sender and chat of
|
|
messages and events is now a different type**. If you were using `isinstance` to check the types,
|
|
you will need to update that code. However, if you were accessing things like the ``first_name``
|
|
or ``username``, you will be fine.
|
|
|
|
Raw API is not affected by this change. When using it, the raw :tl:`User`, :tl:`Chat` and
|
|
:tl:`Channel` are still returned.
|
|
|
|
For friendly methods and events, There are now two main entity types, `User` and `Chat`.
|
|
`User`\ s are active entities which can send messages and interact with eachother. There is an
|
|
account controlling them. `Chat`\ s are passive entities where multiple users can join and
|
|
interact with each other. This includes small groups, supergroups, and broadcast channels.
|
|
|
|
``event.get_sender``, ``event.sender``, ``event.get_chat``, and ``event.chat`` (as well as
|
|
the same methods on ``message`` and elsewhere) now return this new type. The ``sender`` and
|
|
``chat`` is **now always returned** (where it makes sense, so no sender in channel messages),
|
|
even if Telegram did not include information about it in the update. This means you can use
|
|
send messages to ``event.chat`` without worrying if Telegram included this information or not,
|
|
or even access ``event.chat.id``. This was often a papercut. However, if you need other
|
|
information like the title, you might still need to use ``await event.get_chat()``, which is
|
|
used to signify an API call might be necessary.
|
|
|
|
``event.get_input_sender``, ``event.input_sender``, ``message.get_input_sender`` and
|
|
``message.input_sender`` (among other variations) have been removed. Instead, a new ``compact``
|
|
method has been added to the new `User` and `Chat` types, which can be used to obtain a compact
|
|
representation of the sender. The "input" terminology is confusing for end-users, as it's mostly
|
|
an implementation detail of friendly methods. Because the return type would've been different
|
|
had these methods been kept, one would have had to review code using them regardless.
|
|
|
|
What this means is that, if you now want a compact way to store a user or chat for later use,
|
|
you should use ``compact``:
|
|
|
|
.. code-block:: python
|
|
|
|
compacted_user = message.sender.compact()
|
|
# store compacted_user in a database or elsewhere for later use
|
|
|
|
Public methods accept this type as input parameters. This means you can send messages to a
|
|
compacted user or chat, for example.
|
|
|
|
``event.is_private``, ``event.is_group`` and ``event.is_channel`` have **been removed** (among
|
|
other variations, such as in ``message``). It didn't make much sense to ask "is this event a
|
|
group", and there is no such thing as "group messages" currently either. Instead, it's sensible
|
|
to ask if the sender of a message is a group, or the chat of an event is a channel. New properties
|
|
have been added to both the `User` and `Chat` classes:
|
|
|
|
* ``.is_user`` will always be `True` for `User` and `False` for `Chat`.
|
|
* ``.is_group`` will be `False` for `User` and be `True` for small group chats and supergroups.
|
|
* ``.is_broadcast`` will be `False` for `User` and `True` for broadcast channels and broadcast groups.
|
|
|
|
Because the properties exist both in `User` and `Chat`, you do not need use `isinstance` to check
|
|
if a sender is a channel or if a chat is a user.
|
|
|
|
Some fields of the new `User` type differ from the naming or value type of its raw API counterpart:
|
|
|
|
* ``user.restriction_reason`` has been renamed to ``restriction_reasons`` (with a trailing **s**)
|
|
and now always returns a list.
|
|
* ``user.bot_chat_history`` has been renamed to ``user.bot_info.chat_history_access``.
|
|
* ``user.bot_nochats`` has been renamed to ``user.bot_info.private_only``.
|
|
* ``user.bot_inline_geo`` has been renamed to ``user.bot_info.inline_geo``.
|
|
* ``user.bot_info_version`` has been renamed to ``user.bot_info.version``.
|
|
* ``user.bot_inline_placeholder`` has been renamed to ``user.bot_info.inline_placeholder``.
|
|
|
|
The new ``user.bot_info`` field will be `None` for non-bots. The goal is to unify where this
|
|
information is found and reduce clutter in the main ``user`` type.
|
|
|
|
Some fields of the new `Chat` type differ from the naming or value type of its raw API counterpart:
|
|
|
|
* ``chat.date`` is currently not available. It's either the chat creation or join date, but due
|
|
to this inconsistency, it's not included to allow for a better solution in the future.
|
|
* ``chat.has_link`` is currently not available, to allow for a better alternative in the future.
|
|
* ``chat.has_geo`` is currently not available, to allow for a better alternative in the future.
|
|
* ``chat.call_active`` is currently not available, until it's decided what to do about calls.
|
|
* ``chat.call_not_empty`` is currently not available, until it's decided what to do about calls.
|
|
* ``chat.version`` was removed. It's an implementation detail.
|
|
* ``chat.min`` was removed. It's an implementation detail.
|
|
* ``chat.deactivated`` was removed. It's redundant with ``chat.migrated_to``.
|
|
* ``chat.forbidden`` has been added as a replacement for ``isinstance(chat, (ChatForbidden, ChannelForbidden))``.
|
|
* ``chat.forbidden_until`` has been added as a replacement for ``until_date`` in forbidden chats.
|
|
* ``chat.restriction_reason`` has been renamed to ``restriction_reasons`` (with a trailing **s**)
|
|
and now always returns a list.
|
|
* ``chat.migrated_to`` no longer returns a raw type, and instead returns this new `Chat` type.
|
|
|
|
If you have a need for these, please step in, and explain your use case, so we can work together
|
|
to implement a proper design.
|
|
|
|
Both the new `User` and `Chat` types offer a ``fetch`` method, which can be used to refetch the
|
|
instance with fresh information, including the full information about the user (such as the user's
|
|
biography or a chat's about description).
|
|
|
|
|
|
Using a flat list to define buttons will now create rows and not columns
|
|
------------------------------------------------------------------------
|
|
|
|
When sending a message with buttons under a bot account, passing a flat list such as the following:
|
|
|
|
.. code-block:: python
|
|
|
|
bot.send_message(chat, message, buttons=[
|
|
Button.inline('top'),
|
|
Button.inline('middle'),
|
|
Button.inline('bottom'),
|
|
])
|
|
|
|
Will now send a message with 3 rows of buttons, instead of a message with 3 columns (old behaviour).
|
|
If you still want the old behaviour, wrap the list inside another list:
|
|
|
|
.. code-block:: python
|
|
|
|
bot.send_message(chat, message, buttons=[[
|
|
# +
|
|
Button.inline('left'),
|
|
Button.inline('center'),
|
|
Button.inline('right'),
|
|
]])
|
|
#+
|
|
|
|
|
|
Changes to the string and to_dict representation
|
|
------------------------------------------------
|
|
|
|
The string representation of raw API objects will now have its "printing depth" limited, meaning
|
|
very large and nested objects will be easier to read.
|
|
|
|
If you want to see the full object's representation, you should instead use Python's builtin
|
|
``repr`` method.
|
|
|
|
The ``.stringify`` method remains unchanged.
|
|
|
|
Here's a comparison table for a convenient overview:
|
|
|
|
+-------------------+---------------------------------------------+---------------------------------------------+
|
|
| | Telethon v1.x | Telethon v2.x |
|
|
+-------------------+-------------+--------------+----------------+-------------+--------------+----------------+
|
|
| | ``__str__`` | ``__repr__`` | ``.stringify`` | ``__str__`` | ``__repr__`` | ``.stringify`` |
|
|
+-------------------+-------------+--------------+----------------+-------------+--------------+----------------+
|
|
| Useful? | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
+-------------------+-------------+--------------+----------------+-------------+--------------+----------------+
|
|
| Multiline? | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ |
|
|
+-------------------+-------------+--------------+----------------+-------------+--------------+----------------+
|
|
| Shows everything? | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ |
|
|
+-------------------+-------------+--------------+----------------+-------------+--------------+----------------+
|
|
|
|
Both of the string representations may still change in the future without warning, as Telegram
|
|
adds, changes or removes fields. It should only be used for debugging. If you need a persistent
|
|
string representation, it is your job to decide which fields you care about and their format.
|
|
|
|
The ``Message`` representation now contains different properties, which should be more useful and
|
|
less confusing.
|
|
|
|
|
|
Changes on how to configure a different connection mode
|
|
-------------------------------------------------------
|
|
|
|
The ``connection`` parameter of the ``TelegramClient`` now expects a string, and not a type.
|
|
The supported values are:
|
|
|
|
* ``'full'``
|
|
* ``'intermediate'``
|
|
* ``'abridged'``
|
|
* ``'obfuscated'``
|
|
* ``'http'``
|
|
|
|
The value chosen by the library is left as an implementation detail which may change. However,
|
|
you can force a certain mode by explicitly configuring it. If you don't want to hardcode the
|
|
string, you can import these values from the new ``telethon.enums`` module:
|
|
|
|
.. code-block:: python
|
|
|
|
client = TelegramClient(..., connection='tcp')
|
|
|
|
# or
|
|
|
|
from telethon.enums import ConnectionMode
|
|
client = TelegramClient(..., connection=ConnectionMode.TCP)
|
|
|
|
You may have noticed there's currently no alternative for ``TcpMTProxy``. This mode has been
|
|
broken for some time now (see `issue #1319 <https://github.com/LonamiWebs/Telethon/issues/1319>`__)
|
|
anyway, so until there's a working solution, the mode is not supported. Pull Requests are welcome!
|
|
|
|
|
|
The to_json method on objects has been removed
|
|
----------------------------------------------
|
|
|
|
This was not very useful, as most of the time, you'll probably be having other data along with the
|
|
object's JSON. It simply saved you an import (and not even always, in case you wanted another
|
|
encoder). Use ``json.dumps(obj.to_dict())`` instead.
|
|
|
|
|
|
The Conversation API has been removed
|
|
-------------------------------------
|
|
|
|
This API had certain shortcomings, such as lacking persistence, poor interaction with other event
|
|
handlers, and overcomplicated usage for anything beyond the simplest case.
|
|
|
|
It is not difficult to write your own code to deal with a conversation's state. A simple
|
|
`Finite State Machine <https://stackoverflow.com/a/62246569/>`__ inside your handlers will do
|
|
just fine This approach can also be easily persisted, and you can adjust it to your needs and
|
|
your handlers much more easily.
|
|
|
|
// TODO provide standalone alternative for this?
|
|
|
|
|
|
Certain client properties and methods are now private or no longer exist
|
|
------------------------------------------------------------------------
|
|
|
|
The ``client.loop`` property has been removed. ``asyncio`` has been moving towards implicit loops,
|
|
so this is the next step. Async methods can be launched with the much simpler ``asyncio.run`` (as
|
|
opposed to the old ``client.loop.run_until_complete``).
|
|
|
|
The ``client.upload_file`` method has been removed. It's a low-level method users should not need
|
|
to use. Its only purpose could have been to implement a cache of sorts, but this is something the
|
|
library needs to do, not the users.
|
|
|
|
The methods to deal with folders have been removed. The goal is to find and offer a better
|
|
interface to deal with both folders and archived chats in the future if there is demand for it.
|
|
This includes the removal of ``client.edit_folder``, ``Dialog.archive``, ``Dialog.archived``, and
|
|
the ``archived`` parameter of ``client.get_dialogs``. The ``folder`` parameter remains as it's
|
|
unlikely to change.
|
|
|
|
|
|
Deleting messages now returns a more useful value
|
|
-------------------------------------------------
|
|
|
|
It used to return a list of :tl:`messages.affectedMessages` which I expect very little people were
|
|
actually using. Now it returns an ``int`` value indicating the number of messages that did exist
|
|
and were deleted.
|
|
|
|
|
|
Changes to the methods to retrieve participants
|
|
-----------------------------------------------
|
|
|
|
The "aggressive" hack in ``get_participants`` (and ``iter_participants``) is now gone.
|
|
It was not reliable, and was a cause of flood wait errors.
|
|
|
|
The ``search`` parameter is no longer ignored when ``filter`` is specified.
|
|
|
|
|
|
The total value when getting participants has changed
|
|
-----------------------------------------------------
|
|
|
|
Before, it used to always be the total amount of people inside the chat. Now the filter is also
|
|
considered. If you were running ``client.get_participants`` with a ``filter`` other than the
|
|
default and accessing the ``list.total``, you will now get a different result. You will need to
|
|
perform a separate request with no filter to fetch the total without filter (this is what the
|
|
library used to do).
|
|
|
|
|
|
Changes to editing messages
|
|
---------------------------
|
|
|
|
Before, calling ``message.edit()`` would completely ignore your attempt to edit a message if the
|
|
message had a forward header or was not outgoing. This is no longer the case. It is now the user's
|
|
responsibility to check for this.
|
|
|
|
However, most likely, you were already doing the right thing (or else you would've experienced a
|
|
"why is this not being edited", which you would most likely consider a bug rather than a feature).
|
|
|
|
When using ``client.edit_message``, you now must always specify the chat and the message (or
|
|
message identifier). This should be less "magic". As an example, if you were doing this before:
|
|
|
|
.. code-block:: python
|
|
|
|
await client.edit_message(message, 'new text')
|
|
|
|
You now have to do the following:
|
|
|
|
.. code-block:: python
|
|
|
|
await client.edit_message(message.input_chat, message.id, 'new text')
|
|
|
|
# or
|
|
|
|
await message.edit('new text')
|
|
|
|
|
|
Signing in no longer sends the code
|
|
-----------------------------------
|
|
|
|
``client.sign_in()`` used to run ``client.send_code_request()`` if you only provided the phone and
|
|
not the code. It no longer does this. If you need that convenience, use ``client.start()`` instead.
|
|
|
|
|
|
The client.disconnected property has been removed
|
|
-------------------------------------------------
|
|
|
|
``client.run_until_disconnected()`` should be used instead.
|
|
|
|
|
|
The TelegramClient is no longer made out of mixins
|
|
--------------------------------------------------
|
|
|
|
If you were relying on any of the individual mixins that made up the client, such as
|
|
``UserMethods`` inside the ``telethon.client`` subpackage, those are now gone.
|
|
There is a single ``TelegramClient`` class now, containing everything you need.
|
|
|
|
|
|
The takeout context-manager has changed
|
|
---------------------------------------
|
|
|
|
It no longer has a finalize. All the requests made by the client in the same task will be wrapped,
|
|
not only those made through the proxy client returned by the context-manager.
|
|
|
|
This cleans up the (rather hacky) implementation, making use of Python's ``contextvar``. If you
|
|
still need the takeout session to persist, you should manually use the ``begin_takeout`` and
|
|
``end_takeout`` method.
|
|
|
|
If you want to ignore the currently-active takeout session in a task, toggle the following context
|
|
variable:
|
|
|
|
.. code-block:: python
|
|
|
|
telethon.ignore_takeout.set(True)
|
|
|
|
|
|
CdnDecrypter has been removed
|
|
-----------------------------
|
|
|
|
It was not really working and was more intended to be an implementation detail than anything else.
|
|
|
|
|
|
URL buttons no longer open the web-browser
|
|
------------------------------------------
|
|
|
|
Now the URL is returned. You can still use ``webbrowser.open`` to get the old behaviour.
|
|
|
|
|
|
---
|
|
|
|
todo update send_message and send_file docs (well review all functions)
|
|
|
|
album overhaul. use a list of Message instead.
|
|
|
|
is_connected is now a property (consistent with the rest of ``is_`` properties)
|
|
|
|
send_code_request now returns a custom type (reducing raw api).
|
|
sign_in no longer has phone or phone_hash (these are impl details, and now it's less error prone). also mandatory code=. also no longer is a no-op if already logged in. different error for sign up required.
|
|
send code / sign in now only expect a single phone. resend code with new phone is send code, not resend.
|
|
sign_up code is also now a kwarg. and no longer noop if already loggedin.
|
|
start also mandates phone= or password= as kwarg.
|
|
qrlogin expires has been replaced with timeout and expired for parity with tos and auth. the goal is to hide the error-prone system clock and instead use asyncio's clock. recreate was removed (just call qr_login again; parity with get_tos). class renamed to QrLogin. now must be used in a contextmgr to prevent misuse.
|