mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 09:26:37 +03:00
Expand docs on migration guide
This commit is contained in:
parent
22f15303a5
commit
495de58925
|
@ -43,3 +43,20 @@ Glossary
|
||||||
Mobile Transport Protocol used to interact with Telegram's API.
|
Mobile Transport Protocol used to interact with Telegram's API.
|
||||||
|
|
||||||
.. seealso:: The :doc:`../concepts/botapi-vs-mtproto` concept.
|
.. seealso:: The :doc:`../concepts/botapi-vs-mtproto` concept.
|
||||||
|
|
||||||
|
login
|
||||||
|
Used to refer to the login process as a whole, as opposed to the action to :term:`sign in`.
|
||||||
|
The "login code" or "login token" get their name because they belong to the login process.
|
||||||
|
|
||||||
|
sign in
|
||||||
|
Used to refer to the action to sign into either a user or bot account, as opposed to the :term:`login` process.
|
||||||
|
Likewise, "sign out" is used to signify that the authorization should stop being valid.
|
||||||
|
|
||||||
|
layer
|
||||||
|
When Telegram releases new features, it does so by releasing a new "layer".
|
||||||
|
The different layers let Telegram know what a client is capable of and how it should respond to requests.
|
||||||
|
|
||||||
|
TL
|
||||||
|
Type Language
|
||||||
|
File format used by Telegram to define all the types and requests available in a :term:`layer`.
|
||||||
|
Telegram's site has an `Overview of the TL language <https://core.telegram.org/mtproto/TL>`_.
|
||||||
|
|
|
@ -27,6 +27,7 @@ Filtering events
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
There is no way to tell Telegram to only send certain updates.
|
There is no way to tell Telegram to only send certain updates.
|
||||||
|
Telegram sends all updates to connected active clients as they occur.
|
||||||
Telethon must be received and process all updates to ensure correct ordering.
|
Telethon must be received and process all updates to ensure correct ordering.
|
||||||
|
|
||||||
Filters are not magic.
|
Filters are not magic.
|
||||||
|
|
|
@ -1,4 +1,292 @@
|
||||||
Migrating from v1 to v2
|
Migrating from v1 to v2
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
WIP!
|
.. currentmodule:: telethon
|
||||||
|
|
||||||
|
v2 is a complete reboot of Telethon v1.
|
||||||
|
Because a lot of the library has suffered radical changes, there are no plans to provide "bridge" methods emulating the old interface.
|
||||||
|
Doing so would take a lot of extra time and energy, and it's honestly not fun.
|
||||||
|
|
||||||
|
What this means is that your v1 code very likely won't run in v2.
|
||||||
|
Sorry.
|
||||||
|
I hope you can use this opportunity to shake up your dusty code into a cleaner design, too.
|
||||||
|
|
||||||
|
The common theme in v2 could be described as "no bullshit".
|
||||||
|
|
||||||
|
v1 had grown a lot of features.
|
||||||
|
A lot of them did a lot of things, at all once, in slightly different ways.
|
||||||
|
Semver allows additions, so v2 will start out smaller and grow in a controlled manner.
|
||||||
|
|
||||||
|
Custom types were a frankestein monster, combining both raw and manually-defined properties in hacky ways.
|
||||||
|
Type hinting was an unmaintained disaster.
|
||||||
|
Features such as file IDs, proxies and a lot of utilities were pretty much abandoned.
|
||||||
|
|
||||||
|
The several attempts at making v2 a reality over the years starting from the top did not work out.
|
||||||
|
A bottom-up approach was needed.
|
||||||
|
So a full rewrite was warranted.
|
||||||
|
|
||||||
|
TLSharp was Telethon's seed.
|
||||||
|
Telethon v0 was needed to learn Python at all.
|
||||||
|
Telethon v1 was necessary to learn what was a good design, and what wasn't.
|
||||||
|
This inspired `grammers <https://gramme.rs>`_, a Rust re-implementation with a thought-out design.
|
||||||
|
Telethon v2 completes the loop by porting grammers back to Python, now built with years of experience in the Telegram protocol.
|
||||||
|
|
||||||
|
It turns out static type checking is a very good idea for long-running projects.
|
||||||
|
So I strongly encourage you to use `mypy <https://www.mypy-lang.org/>`_ when developing code with Telethon v2.
|
||||||
|
I can guarantee you will into far less problems.
|
||||||
|
|
||||||
|
Without further ado, let's take a look at the biggest changes.
|
||||||
|
This list may not be exhaustive, but it should give you an idea on what to expect.
|
||||||
|
|
||||||
|
|
||||||
|
Complete project restructure
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
The public modules under the ``telethon`` now make actual sense.
|
||||||
|
|
||||||
|
* The root ``telethon`` package contains the basics like the :class:`Client` and :class:`RpcError`.
|
||||||
|
* :mod:`telethon.types` contains all the types, for your tpye-hinting needs.
|
||||||
|
* :mod:`telethon.events` contains all the events.
|
||||||
|
* :mod:`telethon.events.filters` contains all the event filters.
|
||||||
|
* :mod:`telethon.session` contains the session storages, should you choose to build a custom one.
|
||||||
|
* :data:`telethon.errors` is no longer a module.
|
||||||
|
It's actually a factory object returning new error types on demand.
|
||||||
|
This means you don't need to wait for new library versions to be released to catch them.
|
||||||
|
|
||||||
|
This was also a good opportunity to remove a lot of modules that were not supposed to public in their entirety:
|
||||||
|
``.crypto``, ``.extensions``, ``.network``, ``.custom``, ``.functions``, ``.helpers``, ``.hints``, ``.password``, ``.requestiter``, ``.sync``, ``.types``, ``.utils``.
|
||||||
|
|
||||||
|
|
||||||
|
Raw API is now private
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
v2 aims to comply with `Semantic Versioning <https://semver.org/>`.
|
||||||
|
This is impossible because Telegram is a live service that can change things any time.
|
||||||
|
But we can get pretty close.
|
||||||
|
|
||||||
|
In v1, minor version changes bumped Telegram's :term:`layer`.
|
||||||
|
This technically violated semver, because they were part of a public module.
|
||||||
|
|
||||||
|
To allow for new layers to be added without the need for major releases, ``telethon._tl`` is instead private.
|
||||||
|
Here's the recommended way to import and use it now:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from telethon import _tl as tl
|
||||||
|
|
||||||
|
was_reset = await client(tl.functions.account.reset_wall_papers())
|
||||||
|
|
||||||
|
if isinstance(chat, tl.abcs.User):
|
||||||
|
if isinstance(chat, tl.types.UserEmpty):
|
||||||
|
return
|
||||||
|
# chat is tl.types.User
|
||||||
|
|
||||||
|
There are three modules (four, if you count ``core``, which you probably should not use).
|
||||||
|
Each of them can have an additional namespace (as seen above with ``account.``).
|
||||||
|
|
||||||
|
* ``tl.functions`` contains every :term:`TL` definition treated as a function.
|
||||||
|
The naming convention now follows Python's, and are ``snake_case``.
|
||||||
|
They're no longer a class with attributes.
|
||||||
|
They serialize the request immediately.
|
||||||
|
* ``tl.abcs`` contains every abstract class, the "boxed" types from Telegram.
|
||||||
|
You can use these for your type-hinting needs.
|
||||||
|
* ``tl.types`` contains concrete instances, the "bare" types Telegram actually returns.
|
||||||
|
You'll probably use these with :func:`isinstance` a lot.
|
||||||
|
All types use :term:`__slots__` to save space.
|
||||||
|
This means you can't add extra fields to these at runtime unless you subclass.
|
||||||
|
|
||||||
|
|
||||||
|
Unified client iter and get methods
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
The client no longer has ``client.iter_...`` methods.
|
||||||
|
|
||||||
|
Instead, the return a type that supports both :keyword:`await` and :keyword:`async for`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
messages = await client.get_messages(chat, 100)
|
||||||
|
# or
|
||||||
|
async for message in client.get_messages(chat, 100):
|
||||||
|
...
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
:meth:`Client.get_messages` no longer has funny rules for the ``limit`` either.
|
||||||
|
If you ``await`` it without limit, it will probably take a long time to complete.
|
||||||
|
This is in contrast to v1, where ``get`` defaulted to 1 message and ``iter`` to no limit.
|
||||||
|
|
||||||
|
|
||||||
|
Removed client methods and properties
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
.. rubric:: No ``client.parse_mode`` property.
|
||||||
|
|
||||||
|
Instead, you need to specify how the message text should be interpreted every time.
|
||||||
|
In :meth:`~Client.send_message`, use ``text=``, ``markdown=`` or ``html=``.
|
||||||
|
In :meth:`~Client.send_file` and friends, use one of the ``caption`` parameters.
|
||||||
|
|
||||||
|
.. rubric:: No ``client.loop`` property.
|
||||||
|
|
||||||
|
Instead, you can use :func:`asyncio.get_running_loop`.
|
||||||
|
|
||||||
|
.. rubric:: No ``client.conversation()`` method.
|
||||||
|
|
||||||
|
Instead, you will need to `design your own FSM <https://stackoverflow.com/a/62246569>`_.
|
||||||
|
The simplest approach could be using a global ``states`` dictionary storing the next function to call:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
states = {}
|
||||||
|
|
||||||
|
@client.on(events.NewMessage)
|
||||||
|
async def conversation_entry_point(event):
|
||||||
|
if fn := state.get(event.sender.id):
|
||||||
|
await fn(event)
|
||||||
|
else:
|
||||||
|
await event.respond('Hi! What is your name?')
|
||||||
|
state[event.sender.id] = handle_name
|
||||||
|
|
||||||
|
async def handle_name(event):
|
||||||
|
await event.respond('What is your age?')
|
||||||
|
states[event.sender.id] = partial(handle_age, name=event.text)
|
||||||
|
|
||||||
|
async def handle_age(event, name):
|
||||||
|
age = event.text
|
||||||
|
await event.respond(f'Hi {name}, I am {age} too!')
|
||||||
|
del states[event.sender.id]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
No message.raw_text or message.message
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Messages no longer have ``.raw_text`` or ``.message`` properties.
|
||||||
|
|
||||||
|
Instead, you can access the :attr:`types.Message.text`,
|
||||||
|
:attr:`~types.Message.text_markdown` or :attr:`~types.Message.text_html`.
|
||||||
|
These names aim to be consistent with ``caption_markdown`` and ``caption_html``.
|
||||||
|
|
||||||
|
In v1, messages coming from a client used that client's parse mode as some sort of "global state".
|
||||||
|
Based on the client's parse mode, v1 ``message.text`` property would return different things.
|
||||||
|
But not *all* messages did this!
|
||||||
|
Those coming from the raw API had no client, so ``text`` couldn't know how to format the message.
|
||||||
|
|
||||||
|
Overall, the old design made the parse mode be pretty hidden.
|
||||||
|
This was not very intuitive and also made it very awkward to combine multiple parse modes.
|
||||||
|
|
||||||
|
|
||||||
|
Event and filters are now separate
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Event types are no longer callable and do not have filters inside them.
|
||||||
|
There is no longer nested ``class Event`` inside them either.
|
||||||
|
|
||||||
|
Instead, the event type itself is what the handler will actually be called with.
|
||||||
|
Because filters are separate, there is no longer a need for v1 ``@events.register`` either.
|
||||||
|
|
||||||
|
Filters are now normal functions that work with any event.
|
||||||
|
Of course, this doesn't mean all filters make sense for all events.
|
||||||
|
But you can use them in an unified manner.
|
||||||
|
|
||||||
|
Filters no longer support asynchronous operations, which removes a footgun.
|
||||||
|
This was most commonly experienced when using usernames as the ``chats`` filter in v1, and getting flood errors you couldn't handle.
|
||||||
|
In v2, you must pass a list of identifiers.
|
||||||
|
This means getting those identifiers is up to you, and you can handle it in a way that is appropriated for your application.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
In-depth explanation for :doc:`/concepts/updates`.
|
||||||
|
|
||||||
|
|
||||||
|
Streamlined chat, input_chat and chat_id
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
The same goes for ``sender``, ``input_sender`` and ``sender_id``.
|
||||||
|
And also for ``get_chat``, ``get_input_chat``, ``get_sender`` and ``get_input_sender``.
|
||||||
|
Yeah, it was bad.
|
||||||
|
|
||||||
|
Instead, events with chat information now *always* have a ``.chat``, with *at least* the ``.id``.
|
||||||
|
The same is true for the ``.sender``, as long as the event has one with at least the user identifier.
|
||||||
|
|
||||||
|
This doesn't mean the ``.chat`` or ``.sender`` will have all the information.
|
||||||
|
Telegram may still choose to send their ``min`` version with only basic details.
|
||||||
|
But it means you don't have to remember 5 different ways of using chats.
|
||||||
|
|
||||||
|
To replace the concept of "input chats", v2 introduces :class:`types.PackedChat`.
|
||||||
|
A "packed chat" is a chat with *just* enough information that you can use it without relying on Telethon's cache.
|
||||||
|
This is the most efficient way to call methods like :meth:`Client.send_message` too.
|
||||||
|
|
||||||
|
The concept of "marked IDs" also no longer exists.
|
||||||
|
This means v2 no longer supports the ``-`` or ``-100`` prefixes on identifiers.
|
||||||
|
:tl:`Peer`-wrapping is gone, too.
|
||||||
|
Instead, you're strongly encouraged to use :class:`types.PackedChat` instances.
|
||||||
|
|
||||||
|
The concepts of of "entity" or "peer" are unified to simply :term:`chat`.
|
||||||
|
Overall, dealing with users, groups and channels should feel a lot more natural.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
In-depth explanation for :doc:`/concepts/chats`.
|
||||||
|
|
||||||
|
|
||||||
|
Session cache no longer exists
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
At least, not the way it did before.
|
||||||
|
|
||||||
|
The v1 cache that allowed you to use just chat identifiers to call methods is no longer saved to disk.
|
||||||
|
|
||||||
|
Sessions now only contain crucial information to have a working client.
|
||||||
|
This includes the server address, authorization key, update state, and some very basic details.
|
||||||
|
|
||||||
|
To work around this, you can use :class:`types.PackedChat`, which is designed to be easy to store.
|
||||||
|
This means your application can choose the best way to deal with them rather than being forced into Telethon's session.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
In-depth explanation for :doc:`/concepts/sessions`.
|
||||||
|
|
||||||
|
|
||||||
|
StringSession no longer exists
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
If you need to serialize the session data to a string, you can use something like `jsonpickle <https://pypi.org/project/jsonpickle/>`_.
|
||||||
|
Or even the built-in :mod:`pickle` followed by :mod:`base64` or just :meth:`bytes.hex`.
|
||||||
|
But be aware that these approaches probably will not be compatible with additions to the :class:`~session.Session`.
|
||||||
|
|
||||||
|
|
||||||
|
TelegramClient renamed to Client
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
You can rename it with :keyword:`as` during import if you want to use the old name.
|
||||||
|
|
||||||
|
Python allows using namespaces via packages and modules.
|
||||||
|
Therefore, the full name :class:`telethon.Client` already indicates it's from ``telethon``, so the old name was redundant.
|
||||||
|
|
||||||
|
|
||||||
|
Changes to start and client context-manager
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
You can no longer ``start()`` the client.
|
||||||
|
|
||||||
|
Instead, you will need to first :meth:`~Client.connect` and then start the :meth:`~Client.interactive_login`.
|
||||||
|
|
||||||
|
In v1, the when using the client as a context-manager, ``start()`` was called.
|
||||||
|
Since that method no longer exists, it now instead only :meth:`~Client.connect` and :meth:`~Client.disconnect`.
|
||||||
|
|
||||||
|
This means you won't get annoying prompts in your terminal if the session was not authorized.
|
||||||
|
It also means you can now use the context manager even with custom login flows.
|
||||||
|
|
||||||
|
The old ``sign_in()`` method also sent the code, which was rather confusing.
|
||||||
|
Instead, you must now :meth:`~Client.request_login_code` as a separate operation.
|
||||||
|
|
||||||
|
The old ``log_out`` was also renamed to :meth:`~Client.sign_out` for consistency with :meth:`~Client.sign_in`.
|
||||||
|
|
||||||
|
|
||||||
|
No telethon.sync hack
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
You can no longer ``import telethon.sync`` to have most calls wrapped in :meth:`asyncio.loop.run_until_complete` for you.
|
||||||
|
|
|
@ -38,7 +38,7 @@ Tests live under ``tests/``.
|
||||||
|
|
||||||
The implementation consists of a parser and a code generator.
|
The implementation consists of a parser and a code generator.
|
||||||
|
|
||||||
The parser is able to read parse ``.tl`` files (Type-Language definition files).
|
The parser is able to read parse ``.tl`` files (:term:`Type Language` definition files).
|
||||||
It doesn't do anything with the files other than to represent the content as Python objects.
|
It doesn't do anything with the files other than to represent the content as Python objects.
|
||||||
|
|
||||||
The code generator uses the parsed definitions to generate Python code.
|
The code generator uses the parsed definitions to generate Python code.
|
||||||
|
|
|
@ -384,6 +384,8 @@ class Client:
|
||||||
|
|
||||||
:param file:
|
:param file:
|
||||||
The output file path or :term:`file-like object`.
|
The output file path or :term:`file-like object`.
|
||||||
|
Note that the extension is not automatically added to the path.
|
||||||
|
You can get the file extension with :attr:`telethon.types.File.ext`.
|
||||||
|
|
||||||
.. rubric:: Example
|
.. rubric:: Example
|
||||||
|
|
||||||
|
@ -1020,8 +1022,8 @@ class Client:
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self,
|
self,
|
||||||
chat: ChatLike,
|
chat: ChatLike,
|
||||||
*,
|
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: Optional[bool] = None,
|
||||||
|
@ -1046,7 +1048,7 @@ class Client:
|
||||||
return await send_message(
|
return await send_message(
|
||||||
self,
|
self,
|
||||||
chat,
|
chat,
|
||||||
text=text,
|
text,
|
||||||
markdown=markdown,
|
markdown=markdown,
|
||||||
html=html,
|
html=html,
|
||||||
link_preview=link_preview,
|
link_preview=link_preview,
|
||||||
|
|
|
@ -273,7 +273,7 @@ async def upload(
|
||||||
|
|
||||||
|
|
||||||
async def iter_download(self: Client) -> None:
|
async def iter_download(self: Client) -> None:
|
||||||
pass
|
raise NotImplementedError
|
||||||
# result = self(
|
# result = self(
|
||||||
# functions.upload.get_file(
|
# functions.upload.get_file(
|
||||||
# precise=False,
|
# precise=False,
|
||||||
|
@ -291,4 +291,4 @@ async def iter_download(self: Client) -> None:
|
||||||
|
|
||||||
|
|
||||||
async def download(self: Client, media: MediaLike, file: OutFileLike) -> None:
|
async def download(self: Client, media: MediaLike, file: OutFileLike) -> None:
|
||||||
pass
|
raise NotImplementedError
|
||||||
|
|
|
@ -38,8 +38,8 @@ def parse_message(
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self: Client,
|
self: Client,
|
||||||
chat: ChatLike,
|
chat: ChatLike,
|
||||||
*,
|
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: Optional[bool] = None,
|
||||||
|
|
|
@ -295,5 +295,9 @@ class File(metaclass=NoPublicConstructor):
|
||||||
raw=None,
|
raw=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
async def _read(self, n: int) -> bytes:
|
async def _read(self, n: int) -> bytes:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
@ -33,11 +33,11 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
return getattr(self._raw, "message", None)
|
return getattr(self._raw, "message", None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def html_text(self) -> Optional[str]:
|
def text_html(self) -> Optional[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def markdown_text(self) -> Optional[str]:
|
def text_markdown(self) -> Optional[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Reference in New Issue
Block a user