Rename get_entity to get_profile

This commit is contained in:
Lonami Exo 2022-03-02 12:39:47 +01:00
parent 68d07beb21
commit 3865b7fa53
4 changed files with 208 additions and 231 deletions

View File

@ -1,20 +1,27 @@
.. _entities:
========
Entities
========
===============
Users and Chats
===============
The library widely uses the concept of "entities". An entity will refer
to any :tl:`User`, :tl:`Chat` or :tl:`Channel` object that the API may return
in response to certain methods, such as :tl:`GetUsersRequest`.
The library widely uses the concept of "users" to refer to both real accounts
and bot accounts, as well as the concept of "chats" to refer to groups and
broadcast channels.
The most general term you can use to think about these is "an entity", but
recent versions of the library often prefer to opt for names which better
reflect the intention, such as "dialog" when a previously-existing
conversation is expected, or "profile" when referring to the information about
the user or chat.
.. note::
When something "entity-like" is required, it means that you need to
provide something that can be turned into an entity. These things include,
but are not limited to, usernames, exact titles, IDs, :tl:`Peer` objects,
or even entire :tl:`User`, :tl:`Chat` and :tl:`Channel` objects and even
phone numbers **from people you have in your contact list**.
When something "dialog-like" is required, it means that you need to
provide something that can be used to refer to an open conversation.
These things include, but are not limited to, packed chats, usernames,
integer IDs (identifiers), :tl:`Peer` objects, or even entire :tl:`User`,
:tl:`Chat` and :tl:`Channel` objects and even phone numbers **from people
you have in your contact list**.
To "encounter" an ID, you would have to "find it" like you would in the
normal app. If the peer is in your dialogs, you would need to
@ -23,82 +30,123 @@ in response to certain methods, such as :tl:`GetUsersRequest`.
`client.get_participants(group) <telethon.client.chats.ChatMethods.get_participants>`.
Once you have encountered an ID, the library will (by default) have saved
their ``access_hash`` for you, which is needed to invoke most methods.
its packed version for you, which is needed to invoke most methods.
This is why sometimes you might encounter this error when working with
the library. You should ``except ValueError`` and run code that you know
should work to find the entity.
should work to find the user or chat. You **cannot** use an ID of someone
you haven't interacted with. Because this is more unreliable, packed chats
are recommended instead.
.. contents::
What is an Entity?
==================
What is a User?
===============
A lot of methods and requests require *entities* to work. For example,
you send a message to an *entity*, get the username of an *entity*, and
so on.
A `User <telethon.types._custom.user.User>` can be either a real user account
(some person who has signed up for an account) or a bot account which is
programmed to perform certain actions (created by a developer via
`@BotFather <https://t.me/BotFather>`_).
There are many things that work as entities: usernames, phone numbers,
chat links, invite links, IDs, and the types themselves. That is, you can
use any of those when you see an "entity" is needed.
A lot of methods and requests require user or chats to work. For example,
you can send a message to a *user*, ban a *user* from a group, and so on.
These methods accept more than just `User <telethon.types._custom.user.User>`
as the input parameter. You can also use packed users, usernames, string phone
numbers, or integer IDs, although some have higher cost than others.
When using the username, the library must fetch it first, which can be
expensive. When using the phone number, the library must fetch it first, which
can be expensive. If you plan to use these, it's recommended you manually use
`client.get_profile() <telethon.client.users.UserMethods.get_profile>` to cache
the username or phone number, and then use the value returned instead.
.. note::
Remember that the phone number must be in your contact list before you
can use it.
You should use, **from better to worse**:
The recommended type to use as input parameters to the methods is either a
`User <telethon.types._custom.user.User>` instance or its packed type.
1. Input entities. For example, `event.input_chat
<telethon.tl.custom.chatgetter.ChatGetter.input_chat>`,
`message.input_sender
<telethon.tl.custom.sendergetter.SenderGetter.input_sender>`,
or caching an entity you will use a lot with
``entity = await client.get_input_entity(...)``.
2. Entities. For example, if you had to get someone's
username, you can use ``user`` or ``channel``.
It will work. Only use this option if you already have the entity!
3. IDs. This will always look the entity up from the
cache (the ``*.session`` file caches seen entities).
4. Usernames, phone numbers, and links. The cache will be
used too (unless you force a `client.get_entity()
<telethon.client.users.UserMethods.get_entity>`),
but may make a request if the username, phone, or link
has not been found yet.
In recent versions of the library, the following two are equivalent:
.. code-block:: python
async def handler(event):
await client.send_message(event.sender_id, 'Hi')
await client.send_message(event.input_sender, 'Hi')
In the raw API, users are instances of :tl:`User` (or :tl:`UserEmpty`), which
are returned in response to some requests, such as :tl:`GetUsersRequest`.
There are also variants for use as "input parameters", such as :tl:`InputUser`
and :tl:`InputPeerUser`. You generally **do not need** to worry about these
types unless you're using raw API.
If you need to be 99% sure that the code will work (sometimes it's
simply impossible for the library to find the input entity), or if
you will reuse the chat a lot, consider using the following instead:
What is a Chat?
===============
.. code-block:: python
A `Chat <telethon.types._custom.chat.Chat>` can be a small group chat (the
default group type created by users where many users can join and talk), a
megagroup (also known as "supergroup"), a broadcast channel or a broadcast
group.
async def handler(event):
# This method may make a network request to find the input sender.
# Properties can't make network requests, so we need a method.
sender = await event.get_input_sender()
await client.send_message(sender, 'Hi')
await client.send_message(sender, 'Hi')
The term "chat" is really overloaded in Telegram. The library tries to be
explicit and always use "small group chat", "megagroup" and "broadcast" to
differentiate. However, Telegram's API uses "chat" to refer to both "chat"
(small group chat), and "channel" (megagroup, broadcast or "gigagroup" which
is a broadcast group of type channel).
A lot of methods and requests require a chat to work. For example,
you can get the participants from a *chat*, kick users from a *chat*, and so on.
These methods accept more than just `Chat <telethon.types._custom.chat.Chat>`
as the input parameter. You can also use packed chats, the public link, or
integer IDs, although some have higher cost than others.
When using the public link, the library must fetch it first, which can be
expensive. If you plan to use these, it's recommended you manually use
`client.get_profile() <telethon.client.users.UserMethods.get_profile>` to cache
the link, and then use the value returned instead.
.. note::
The link of a public chat has the form "t.me/username", where the username
can belong to either an actual user or a public chat.
The recommended type to use as input parameters to the methods is either a
`Chat <telethon.types._custom.chat.Chat>` instance or its packed type.
In the raw API, chats are instances of :tl:`Chat` and :tl:`Channel` (or
:tl:`ChatEmpty`, :tl:`ChatForbidden` and :tl:`ChannelForbidden`), which
are returned in response to some requests, such as :tl:`messages.GetChats`
and :tl:`channels.GetChannels`. There are also variants for use as "input
parameters", such as :tl:`InputChannel` and :tl:`InputPeerChannel`. You
generally **do not need** to worry about these types unless you're using raw API.
Getting Entities
================
When to use each term?
======================
The term "dialog" is used when the library expects a reference to an open
conversation (from the list the user sees when they open the application).
The term "profile" is used instead of "dialog" when the conversation is not
expected to exist. Because "dialog" is more specific than "profile", "dialog"
is used where possible instead.
In general, you should not use named arguments for neither "dialogs" or
"profiles", since they're the first argument. The parameter name only exists
for documentation purposes.
The term "chat" is used where a group or broadcast channel is expected. This
includes small groups, megagroups, broadcast channels and broadcast groups.
Telegram's API has, in the past, made a difference between which methods can
be used for "small group chats" and everything else. For example, small group
chats cannot have a public link (they automatically convert to megagroups).
Group permissions also used to be different, but because Telegram may unify
these eventually, the library attempts to hide this distinction. In general,
this is not something you should worry about.
Fetching profile information
============================
Through the use of the :ref:`sessions`, the library will automatically
remember the ID and hash pair, along with some extra information, so
you're able to just do this:
remember the packed users and chats, along with some extra information,
so you're able to just do this:
.. code-block:: python
@ -106,145 +154,37 @@ you're able to just do this:
#
# Dialogs are the "conversations you have open".
# This method returns a list of Dialog, which
# has the .entity attribute and other information.
# has the .user and .chat attributes (among others).
#
# This part is IMPORTANT, because it fills the entity cache.
# This part is IMPORTANT, because it fills the cache.
dialogs = await client.get_dialogs()
# All of these work and do the same.
username = await client.get_entity('username')
username = await client.get_entity('t.me/username')
username = await client.get_entity('https://telegram.dog/username')
# All of these work and do the same, but are more expensive to use.
channel = await client.get_profile('username')
channel = await client.get_profile('t.me/username')
channel = await client.get_profile('https://telegram.dog/username')
contact = await client.get_profile('+34xxxxxxxxx')
# Other kind of entities.
channel = await client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg')
contact = await client.get_entity('+34xxxxxxxxx')
friend = await client.get_entity(friend_id)
# This will work, but only if the ID is in cache.
friend = await client.get_profile(friend_id)
# Getting entities through their ID (User, Chat or Channel)
entity = await client.get_entity(some_id)
# You can be more explicit about the type for said ID by wrapping
# it inside a Peer instance. This is recommended but not necessary.
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
my_user = await client.get_entity(PeerUser(some_id))
my_chat = await client.get_entity(PeerChat(some_id))
my_channel = await client.get_entity(PeerChannel(some_id))
# This is the most reliable way to fetch a profile.
user = await client.get_profile('U.123.456789')
group = await client.get_profile('G.456.0')
broadcast = await client.get_profile('C.789.123456')
.. note::
You **don't** need to get the entity before using it! Just let the
library do its job. Use a phone from your contacts, username, ID or
input entity (preferred but not necessary), whatever you already have.
All methods in the :ref:`telethon-client` call `.get_input_entity()
<telethon.client.users.UserMethods.get_input_entity>` prior
to sending the request to save you from the hassle of doing so manually.
All methods in the :ref:`telethon-client` accept any of the above
prior to sending the request to save you from the hassle of doing so manually.
That way, convenience calls such as `client.send_message('username', 'hi!')
<telethon.client.messages.MessageMethods.send_message>`
become possible.
Every entity the library encounters (in any response to any call) will by
default be cached in the ``.session`` file (an SQLite database), to avoid
performing unnecessary API calls. If the entity cannot be found, additional
calls like :tl:`ResolveUsernameRequest` or :tl:`GetContactsRequest` may be
made to obtain the required information.
Entities vs. Input Entities
===========================
.. note::
This section is informative but worth reading. The library
will transparently handle all of these details for you.
On top of the normal types, the API also make use of what they call their
``Input*`` versions of objects. The input version of an entity (e.g.
:tl:`InputPeerUser`, :tl:`InputChat`, etc.) only contains the minimum
information required from Telegram to identify
who you're referring to: a :tl:`Peer`'s **ID** and **hash**. They
are named like this because they are input parameters in the requests.
Entities' IDs are the same for all user and bot accounts. However, the access
hash is **different for each account**, so trying to reuse the access hash
from one account in another will **not** work.
Sometimes, Telegram only needs to indicate the entity type and their ID. For this purpose, :tl:`Peer` versions of the entities also
exist, which just have the ID. You cannot get the hash out of them since
you should not need it. The library probably has cached it before.
Peers are enough to identify an entity, but they are not enough to make
a request with them. You need to know their hash before you can
"use them", and to know the hash you need to "encounter" them, let it
be in your dialogs, participants, message forwards, etc.
.. note::
You *can* use peers with the library. Behind the scenes, they are
replaced with the input variant. Peers "aren't enough" on their own
, but the library will do some more work to use the right type.
As we just mentioned, API calls don't need to know the whole information
about the entities, only their ID and hash. For this reason, another method,
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
is available. This will always use the cache while possible, making zero API
calls most of the time. When a request is made, if you provided the full
entity, e.g. an :tl:`User`, the library will automatically convert it to the required
:tl:`InputPeer`.
**You should always favour**
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
**over**
`client.get_entity() <telethon.client.users.UserMethods.get_entity>`
for this reason! Calling the latter will always make an API call to get
the most recent information about said entity, but invoking requests don't
need this information, just the :tl:`InputPeer`. Only use
`client.get_entity() <telethon.client.users.UserMethods.get_entity>`
if you need to get actual information, like the username, name, title, etc.
of the entity.
To further simplify the workflow, since the version ``0.16.2`` of the
library, the raw requests you make to the API are also able to call
`client.get_input_entity() <telethon.client.users.UserMethods.get_input_entity>`
wherever needed, so you can even do things like:
.. code-block:: python
await client(SendMessageRequest('username', 'hello'))
The library will call the request's ``.resolve()`` method, which will
resolve ``'username'`` with the appropriate :tl:`InputPeer`. Don't worry if
you don't get this yet, but remember that some of the details here are important.
Full Entities
=============
In addition to :tl:`PeerUser`, :tl:`InputPeerUser`, :tl:`User` (and its
variants for chats and channels), there is also the concept of :tl:`UserFull`.
This full variant has additional information such as whether the user is
blocked, its notification settings, the bio or about of the user, etc.
There is also :tl:`messages.ChatFull` which is the equivalent of full entities
for chats and channels, with the about section of the channel. Note that
the ``users`` field only contains bots for the channel (so clients can
suggest commands to use).
You can get both of these by invoking :tl:`GetFullUser`, :tl:`GetFullChat`
and :tl:`GetFullChannel` respectively.
Accessing Entities
==================
<telethon.client.messages.MessageMethods.send_message>` become possible.
However, it can be expensive to fetch the username every time, so this is
better left for things which are not executed often.
Although it's explicitly noted in the documentation that messages
*subclass* `ChatGetter <telethon.tl.custom.chatgetter.ChatGetter>`
and `SenderGetter <telethon.tl.custom.sendergetter.SenderGetter>`,
some people still don't get inheritance.
this section will explain what this means.
When the documentation says "Bases: `telethon.tl.custom.chatgetter.ChatGetter`"
it means that the class you're looking at, *also* can act as the class it
@ -257,9 +197,9 @@ That means you can do this:
.. code-block:: python
message.is_private
message.chat_id
await message.get_chat()
message.chat
await event.get_chat()
# ...etc
`SenderGetter <telethon.tl.custom.sendergetter.SenderGetter>` is similar:
@ -267,8 +207,8 @@ That means you can do this:
.. code-block:: python
message.user_id
await message.get_input_user()
message.user
await event.get_input_user()
# ...etc
Quite a few things implement them, so it makes sense to reuse the code.
@ -277,11 +217,51 @@ For example, all events (except raw updates) implement `ChatGetter
in some chat.
Packed User and packed Chat
===========================
A packed `User <telethon.types._custom.user.User>` or a packed
`Chat <telethon.types._custom.chat.Chat>` can be thought of as
"a small string reference to the actual user or chat".
It can easily be saved or embedded in the code for later use,
without having to worry if the user is in the session file cache.
This "packed representation" is a compact way to store the type of the User
or Chat (is it a user account, a bot, a broadcast channel…), the identifier,
and the access hash. This "access hash" is something Telegram uses to ensure
that you can actually use this "User" or "Chat" in requests (so you can't just
create some random user identifier and expect it to work).
In the raw API, this is pretty much "input peers", but the library uses the
term "packed user or chat" to refer to its custom type and string
representation.
The User and Chat IDs are the same for all user and bot accounts. However, the
access hash is **different for each account**, so trying to reuse the access
hash from one account in another will **not** work. This also means the packed
representation will only work for the account that created it.
The library needs to have this access hash in some way for it to work.
If it only has an ID and this ID is not in cache, it will not work.
If using the packed representation, the hash is embedded, and will always work.
Every method, including raw API, will automatically convert your types to the
expected input type the API uses, meaning the following will work:
.. code-block:: python
await client(_tl.fn.messages.SendMessage('username', 'hello'))
(This is only a raw API example, there are better ways to send messages.)
Summary
=======
TL;DR; If you're here because of *"Could not find the input entity for"*,
you must ask yourself, "how did I find this entity through official
TL;DR; If you're here because of *"Could not find the input peer for"*,
you must ask yourself, "how did I find this user or chat through official
applications"? Now do the same with the library. Use what applies:
.. code-block:: python
@ -289,7 +269,7 @@ applications"? Now do the same with the library. Use what applies:
# (These examples assume you are inside an "async def")
async with client:
# Does it have a username? Use it!
entity = await client.get_entity(username)
user = await client.get_profile(username)
# Do you have a conversation open with them? Get dialogs.
await client.get_dialogs()
@ -297,16 +277,20 @@ applications"? Now do the same with the library. Use what applies:
# Are they participants of some group? Get them.
await client.get_participants('username')
# Is the entity the original sender of a forwarded message? Get it.
# Is the user the original sender of a forwarded message? Fetch the message.
await client.get_messages('username', 100)
# NOW you can use the ID anywhere!
await client.send_message(123456, 'Hi!')
entity = await client.get_entity(123456)
print(entity)
user = await client.get_profile(123456)
print(user)
Once the library has "seen" the entity, you can use their **integer** ID.
You can't use entities from IDs the library hasn't seen. You must make the
library see them *at least once* and disconnect properly. You know where
the entities are, and you must tell the library. It won't guess for you.
Once the library has "seen" the user or chat, you can use their **integer** ID.
You can't use users or chats from IDs the library hasn't seen. You must make
the library see them *at least once* and disconnect properly. You know where
the user or chat are, and you must tell the library. It won't guess for you.
This is why it's recommended to use the packed versions instead. They will
always work (unless Telegram, for some very unlikely reason, changes the way
using users and chats works, of course).

View File

@ -987,3 +987,4 @@ 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.
"entity" parameters have been renamed to "dialog" (user or chat expected) or "chat" (only chats expected), "profile" (if that makes sense). the goal is to move away from the entity terminology. this is intended to be a documentation change, but because the parameters were renamed, it's breaking. the expected usage of positional arguments is mostly unaffected. this includes the EntityLike hint.
download_media param renamed message to media. iter_download file to media too
less types are supported to get entity (exact names, private links are undocumented but may work). get_entity is get_profile. get_input_entity is gone. get_peer_id is gone (if the isntance needs to be fetched anyway just use get_profile).

View File

@ -3279,57 +3279,49 @@ class TelegramClient:
await client.sign_in(phone, code)
"""
@forward_call(users.get_entity)
async def get_entity(
@forward_call(users.get_profile)
async def get_profile(
self: 'TelegramClient',
entity: 'hints.EntitiesLike') -> 'hints.Entity':
profile: 'hints.DialogsLike') -> 'hints.Entity':
"""
Turns the given entity into a valid Telegram :tl:`User`, :tl:`Chat`
or :tl:`Channel`. You can also pass a list or iterable of entities,
and they will be efficiently fetched from the network.
Turns the given profile reference into a `User <telethon.types._custom.user.User>`
or `Chat <telethon.types._custom.chat.Chat>` instance.
Arguments
entity (`str` | `int` | :tl:`Peer` | :tl:`InputPeer`):
profile (`str` | `int` | :tl:`Peer` | :tl:`InputPeer`):
If a username is given, **the username will be resolved** making
an API call every time. Resolving usernames is an expensive
operation and will start hitting flood waits around 50 usernames
in a short period of time.
If you want to get the entity for a *cached* username, you should
first `get_input_entity(username) <get_input_entity>` which will
use the cache), and then use `get_entity` with the result of the
previous call.
Using phone numbers with strings will fetch your contact list first.
Similar limits apply to invite links, and you should use their
ID instead.
Using integer IDs will only work if the ID is in the session cache.
Using phone numbers (from people in your contact list), exact
names, integer IDs or :tl:`Peer` rely on a `get_input_entity`
first, which in turn needs the entity to be in cache, unless
a :tl:`InputPeer` was passed.
``'me'`` is a special-case to the logged-in account (yourself).
Unsupported types will raise ``TypeError``.
If the entity can't be found, ``ValueError`` will be raised.
If the user or chat can't be found, ``ValueError`` will be raised.
Returns
:tl:`User`, :tl:`Chat` or :tl:`Channel` corresponding to the
input entity. A list will be returned if more than one was given.
`User <telethon.types._custom.user.User>` or `Chat <telethon.types._custom.chat.Chat>`,
depending on the profile requested.
Example
.. code-block:: python
from telethon import utils
me = await client.get_entity('me')
me = await client.get_profile('me')
print(utils.get_display_name(me))
chat = await client.get_input_entity('username')
chat = await client.get_profile('username')
async for message in client.get_messages(chat):
...
# Note that you could have used the username directly, but it's
# good to use get_input_entity if you will reuse it a lot.
# good to use get_profile if you will reuse it a lot.
async for message in client.get_messages('username'):
...

View File

@ -150,9 +150,9 @@ async def is_user_authorized(self: 'TelegramClient') -> bool:
except RpcError:
return False
async def get_entity(
async def get_profile(
self: 'TelegramClient',
entity: 'hints.EntitiesLike') -> 'hints.Entity':
profile: 'hints.DialogsLike') -> 'hints.Entity':
single = not utils.is_list_like(entity)
if single:
entity = (entity,)
@ -222,7 +222,7 @@ async def get_entity(
async def get_input_entity(
self: 'TelegramClient',
peer: 'hints.EntityLike') -> '_tl.TypeInputPeer':
peer: 'hints.DialogLike') -> '_tl.TypeInputPeer':
# Short-circuit if the input parameter directly maps to an InputPeer
try:
return utils.get_input_peer(peer)
@ -281,7 +281,7 @@ async def get_input_entity(
pass
raise ValueError(
'Could not find the input entity for {} ({}). Please read https://'
'Could not find the input peer for {} ({}). Please read https://'
'docs.telethon.dev/en/latest/concepts/entities.html to'
' find out more details.'
.format(peer, type(peer).__name__)
@ -289,7 +289,7 @@ async def get_input_entity(
async def get_peer_id(
self: 'TelegramClient',
peer: 'hints.EntityLike') -> int:
peer: 'hints.DialogLike') -> int:
if isinstance(peer, int):
return utils.get_peer_id(peer)