Change Chat and Sender Getter interface to match new description

This commit is contained in:
Lonami Exo 2022-02-12 11:30:16 +01:00
parent 9bfe4cddf5
commit f0654a0833
2 changed files with 74 additions and 171 deletions

View File

@ -6,138 +6,65 @@ from ... import errors, _tl
class ChatGetter(abc.ABC):
"""
Helper base class that introduces the `chat`, `input_chat`
and `chat_id` properties and `get_chat` and `get_input_chat`
methods.
Helper base class that introduces the chat-related properties and methods.
"""
def __init__(self, chat_peer=None, *, input_chat=None, chat=None, broadcast=None):
self._chat_peer = chat_peer
self._input_chat = input_chat
def __init__(self, chat, client):
self._chat = chat
self._broadcast = broadcast
self._client = None
self._client = client
@property
def chat(self):
"""
Returns the :tl:`User`, :tl:`Chat` or :tl:`Channel` where this object
belongs to. It may be `None` if Telegram didn't send the chat.
Returns the `User` or `Chat` who sent this object, or `None` if there is no chat.
If you only need the ID, use `chat_id` instead.
The chat of an event is only guaranteed to include the ``id``.
If you need the chat to at least have basic information, use `get_chat` instead.
If you need to call a method which needs
this chat, use `input_chat` instead.
If you're using `telethon.events`, use `get_chat()` instead.
Chats obtained through friendly methods (not events) will always have complete
information (so there is no need to use `get_chat` or ``chat.fetch()``).
"""
return self._chat
async def get_chat(self):
"""
Returns `chat`, but will make an API call to find the
chat unless it's already cached.
Returns `chat`, but will make an API call to find the chat unless it's already cached.
If you only need the ID, use `chat_id` instead.
If you need to call a method which needs
this chat, use `get_input_chat()` instead.
"""
# See `get_sender` for information about 'min'.
if (self._chat is None or getattr(self._chat, 'min', None))\
and await self.get_input_chat():
try:
self._chat =\
await self._client.get_entity(self._input_chat)
except ValueError:
await self._refetch_chat()
return self._chat
If you need to call a method which needs this chat, prefer `chat` instead.
@property
def input_chat(self):
"""
This :tl:`InputPeer` is the input version of the chat where the
message was sent. Similarly to `input_sender
<telethon.tl.custom.sendergetter.SenderGetter.input_sender>`, this
doesn't have things like username or similar, but still useful in
some cases.
Telegram may send a "minimal" version of the chat to save on bandwidth when using events.
If you need all the information about the chat upfront, you can use ``chat.fetch()``.
Note that this might not be available if the library doesn't
have enough information available.
"""
return self._input_chat
.. code-block:: python
async def get_input_chat(self):
"""
Returns `input_chat`, but will make an API call to find the
input chat unless it's already cached.
"""
if self.input_chat is None and self.chat_id and self._client:
try:
# The chat may be recent, look in dialogs
target = self.chat_id
async for d in self._client.get_dialogs(100):
if d.id == target:
self._chat = d.entity
self._input_chat = d.input_entity
break
except errors.RpcError:
pass
@client.on(events.NewMessage)
async def handler(event):
# I only need the ID -> use chat_id
chat_id = event.chat_id
return self._input_chat
# I'm going to use the chat in a method -> use chat
await client.send_message(event.chat, 'Hi!')
# I need the chat's title -> use get_chat
chat = await event.get_chat()
print(chat.title)
# I want to see all the information about the chat -> use fetch
chat = await event.chat.fetch()
print(chat.stringify())
# ...
async for message in client.get_messages(chat):
# Here there's no need to fetch the chat - get_messages already did
print(message.chat.stringify())
"""
raise RuntimeError('TODO')
@property
def chat_id(self):
"""
Returns the marked chat integer ID. Note that this value **will
be different** from ``peer_id`` for incoming private messages, since
the chat *to* which the messages go is to your own person, but
the *chat* itself is with the one who sent the message.
TL;DR; this gets the ID that you expect.
If there is a chat in the object, `chat_id` will *always* be set,
which is why you should use it instead of `chat.id <chat>`.
"""
return utils.get_peer_id(self._chat_peer) if self._chat_peer else None
@property
def is_private(self):
"""
`True` if the message was sent as a private message.
Returns `None` if there isn't enough information
(e.g. on `events.MessageDeleted <telethon.events.messagedeleted.MessageDeleted>`).
"""
return isinstance(self._chat_peer, _tl.PeerUser) if self._chat_peer else None
@property
def is_group(self):
"""
True if the message was sent on a group or megagroup.
Returns `None` if there isn't enough information
(e.g. on `events.MessageDeleted <telethon.events.messagedeleted.MessageDeleted>`).
"""
# TODO Cache could tell us more in the future
if self._broadcast is None and hasattr(self.chat, 'broadcast'):
self._broadcast = bool(self.chat.broadcast)
if isinstance(self._chat_peer, _tl.PeerChannel):
if self._broadcast is None:
return None
else:
return not self._broadcast
return isinstance(self._chat_peer, _tl.PeerChat)
@property
def is_channel(self):
"""`True` if the message was sent on a megagroup or channel."""
# The only case where chat peer could be none is in MessageDeleted,
# however those always have the peer in channels.
return isinstance(self._chat_peer, _tl.PeerChannel)
async def _refetch_chat(self):
"""
Re-fetches chat information through other means.
Alias for ``self.chat.id``, but checking if ``chat is not None`` first.
"""
return self._chat.id if self._chat else None

View File

@ -3,89 +3,65 @@ import abc
class SenderGetter(abc.ABC):
"""
Helper base class that introduces the `sender`, `input_sender`
and `sender_id` properties and `get_sender` and `get_input_sender`
methods.
Helper base class that introduces the sender-related properties and methods.
"""
def __init__(self, sender_id=None, *, sender=None, input_sender=None):
self._sender_id = sender_id
def __init__(self, sender, client):
self._sender = sender
self._input_sender = input_sender
self._client = None
self._client = client
@property
def sender(self):
"""
Returns the :tl:`User` or :tl:`Channel` that sent this object.
It may be `None` if Telegram didn't send the sender.
Returns the `User` or `Chat` who sent this object, or `None` if there is no sender.
If you only need the ID, use `sender_id` instead.
The sender of an event is only guaranteed to include the ``id``.
If you need the sender to at least have basic information, use `get_sender` instead.
If you need to call a method which needs
this chat, use `input_sender` instead.
If you're using `telethon.events`, use `get_sender()` instead.
Senders obtained through friendly methods (not events) will always have complete
information (so there is no need to use `get_sender` or ``sender.fetch()``).
"""
return self._sender
async def get_sender(self):
"""
Returns `sender`, but will make an API call to find the
sender unless it's already cached.
Returns `sender`, but will make an API call to find the sender unless it's already cached.
If you only need the ID, use `sender_id` instead.
If you need to call a method which needs
this sender, use `get_input_sender()` instead.
"""
# ``sender.min`` is present both in :tl:`User` and :tl:`Channel`.
# It's a flag that will be set if only minimal information is
# available (such as display name, but username may be missing),
# in which case we want to force fetch the entire thing because
# the user explicitly called a method. If the user is okay with
# cached information, they may use the property instead.
if (self._sender is None or getattr(self._sender, 'min', None)) \
and await self.get_input_sender():
try:
self._sender =\
await self._client.get_entity(self._input_sender)
except ValueError:
await self._refetch_sender()
return self._sender
If you need to call a method which needs this sender, prefer `sender` instead.
@property
def input_sender(self):
"""
This :tl:`InputPeer` is the input version of the user/channel who
sent the message. Similarly to `input_chat
<telethon.tl.custom.chatgetter.ChatGetter.input_chat>`, this doesn't
have things like username or similar, but still useful in some cases.
Telegram may send a "minimal" version of the sender to save on bandwidth when using events.
If you need all the information about the sender upfront, you can use ``sender.fetch()``.
Note that this might not be available if the library can't
find the input chat, or if the message a broadcast on a channel.
"""
return self._input_sender
.. code-block:: python
async def get_input_sender(self):
@client.on(events.NewMessage)
async def handler(event):
# I only need the ID -> use sender_id
sender_id = event.sender_id
# I'm going to use the sender in a method -> use sender
await client.send_message(event.sender, 'Hi!')
# I need the sender's first name -> use get_sender
sender = await event.get_sender()
print(sender.first_name)
# I want to see all the information about the sender -> use fetch
sender = await event.sender.fetch()
print(sender.stringify())
# ...
async for message in client.get_messages(chat):
# Here there's no need to fetch the sender - get_messages already did
print(message.sender.stringify())
"""
Returns `input_sender`, but will make an API call to find the
input sender unless it's already cached.
"""
if self.input_sender is None and self._sender_id and self._client:
await self._refetch_sender()
return self._input_sender
raise RuntimeError('TODO')
@property
def sender_id(self):
"""
Returns the marked sender integer ID, if present.
If there is a sender in the object, `sender_id` will *always* be set,
which is why you should use it instead of `sender.id <sender>`.
"""
return self._sender_id
async def _refetch_sender(self):
"""
Re-fetches sender information through other means.
Alias for ``self.sender.id``, but checking if ``sender is not None`` first.
"""
return self._sender.id if sender else None