mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 09:26:37 +03:00
Merge pull request #595 from LonamiWebs/events
Friendlier update handling through Events
This commit is contained in:
commit
f91b76b063
144
readthedocs/extra/advanced-usage/update-modes.rst
Normal file
144
readthedocs/extra/advanced-usage/update-modes.rst
Normal file
|
@ -0,0 +1,144 @@
|
|||
.. _update-modes:
|
||||
|
||||
============
|
||||
Update Modes
|
||||
============
|
||||
|
||||
|
||||
The library can run in four distinguishable modes:
|
||||
|
||||
- With no extra threads at all.
|
||||
- With an extra thread that receives everything as soon as possible (default).
|
||||
- With several worker threads that run your update handlers.
|
||||
- A mix of the above.
|
||||
|
||||
Since this section is about updates, we'll describe the simplest way to
|
||||
work with them.
|
||||
|
||||
|
||||
Using multiple workers
|
||||
**********************
|
||||
|
||||
When you create your client, simply pass a number to the
|
||||
``update_workers`` parameter:
|
||||
|
||||
``client = TelegramClient('session', api_id, api_hash, update_workers=2)``
|
||||
|
||||
You can set any amount of workers you want. The more you put, the more
|
||||
update handlers that can be called "at the same time". One or two should
|
||||
suffice most of the time, since setting more will not make things run
|
||||
faster most of the times (actually, it could slow things down).
|
||||
|
||||
The next thing you want to do is to add a method that will be called when
|
||||
an `Update`__ arrives:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def callback(update):
|
||||
print('I received', update)
|
||||
|
||||
client.add_update_handler(callback)
|
||||
# do more work here, or simply sleep!
|
||||
|
||||
That's it! This is the old way to listen for raw updates, with no further
|
||||
processing. If this feels annoying for you, remember that you can always
|
||||
use :ref:`working-with-updates` but maybe use this for some other cases.
|
||||
|
||||
Now let's do something more interesting. Every time an user talks to use,
|
||||
let's reply to them with the same text reversed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.types import UpdateShortMessage, PeerUser
|
||||
|
||||
def replier(update):
|
||||
if isinstance(update, UpdateShortMessage) and not update.out:
|
||||
client.send_message(PeerUser(update.user_id), update.message[::-1])
|
||||
|
||||
|
||||
client.add_update_handler(replier)
|
||||
input('Press enter to stop this!')
|
||||
client.disconnect()
|
||||
|
||||
We only ask you one thing: don't keep this running for too long, or your
|
||||
contacts will go mad.
|
||||
|
||||
|
||||
Spawning no worker at all
|
||||
*************************
|
||||
|
||||
All the workers do is loop forever and poll updates from a queue that is
|
||||
filled from the ``ReadThread``, responsible for reading every item off
|
||||
the network. If you only need a worker and the ``MainThread`` would be
|
||||
doing no other job, this is the preferred way. You can easily do the same
|
||||
as the workers like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
while True:
|
||||
try:
|
||||
update = client.updates.poll()
|
||||
if not update:
|
||||
continue
|
||||
|
||||
print('I received', update)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
client.disconnect()
|
||||
|
||||
Note that ``poll`` accepts a ``timeout=`` parameter, and it will return
|
||||
``None`` if other thread got the update before you could or if the timeout
|
||||
expired, so it's important to check ``if not update``.
|
||||
|
||||
This can coexist with the rest of ``N`` workers, or you can set it to ``0``
|
||||
additional workers:
|
||||
|
||||
``client = TelegramClient('session', api_id, api_hash, update_workers=0)``
|
||||
|
||||
You **must** set it to ``0`` (or other number), as it defaults to ``None``
|
||||
and there is a different. ``None`` workers means updates won't be processed
|
||||
*at all*, so you must set it to some value (``0`` or greater) if you want
|
||||
``client.updates.poll()`` to work.
|
||||
|
||||
|
||||
Using the main thread instead the ``ReadThread``
|
||||
************************************************
|
||||
|
||||
If you have no work to do on the ``MainThread`` and you were planning to have
|
||||
a ``while True: sleep(1)``, don't do that. Instead, don't spawn the secondary
|
||||
``ReadThread`` at all like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client = TelegramClient(
|
||||
...
|
||||
spawn_read_thread=False
|
||||
)
|
||||
|
||||
And then ``.idle()`` from the ``MainThread``:
|
||||
|
||||
``client.idle()``
|
||||
|
||||
You can stop it with :kbd:`Control+C`, and you can configure the signals
|
||||
to be used in a similar fashion to `Python Telegram Bot`__.
|
||||
|
||||
As a complete example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def callback(update):
|
||||
print('I received', update)
|
||||
|
||||
client = TelegramClient('session', api_id, api_hash,
|
||||
update_workers=1, spawn_read_thread=False)
|
||||
|
||||
client.connect()
|
||||
client.add_update_handler(callback)
|
||||
client.idle() # ends with Ctrl+C
|
||||
|
||||
|
||||
This is the preferred way to use if you're simply going to listen for updates.
|
||||
|
||||
__ https://lonamiwebs.github.io/Telethon/types/update.html
|
||||
__ https://github.com/python-telegram-bot/python-telegram-bot/blob/4b3315db6feebafb94edcaa803df52bb49999ced/telegram/ext/updater.py#L460
|
|
@ -5,144 +5,130 @@ Working with Updates
|
|||
====================
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
There are plans to make working with updates more friendly. Stay tuned!
|
||||
The library comes with the :mod:`events` module. *Events* are an abstraction
|
||||
over what Telegram calls `updates`__, and are meant to ease simple and common
|
||||
usage when dealing with them, since there are many updates. Let's dive in!
|
||||
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
The library can run in four distinguishable modes:
|
||||
|
||||
- With no extra threads at all.
|
||||
- With an extra thread that receives everything as soon as possible (default).
|
||||
- With several worker threads that run your update handlers.
|
||||
- A mix of the above.
|
||||
|
||||
Since this section is about updates, we'll describe the simplest way to
|
||||
work with them.
|
||||
|
||||
|
||||
Using multiple workers
|
||||
**********************
|
||||
|
||||
When you create your client, simply pass a number to the
|
||||
``update_workers`` parameter:
|
||||
|
||||
``client = TelegramClient('session', api_id, api_hash, update_workers=4)``
|
||||
|
||||
4 workers should suffice for most cases (this is also the default on
|
||||
`Python Telegram Bot`__). You can set this value to more, or even less
|
||||
if you need.
|
||||
|
||||
The next thing you want to do is to add a method that will be called when
|
||||
an `Update`__ arrives:
|
||||
Getting Started
|
||||
***************
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def callback(update):
|
||||
print('I received', update)
|
||||
from telethon import TelegramClient, events
|
||||
|
||||
client.add_update_handler(callback)
|
||||
# do more work here, or simply sleep!
|
||||
client = TelegramClient(..., update_workers=1, spawn_read_thread=False)
|
||||
client.start()
|
||||
|
||||
That's it! Now let's do something more interesting.
|
||||
Every time an user talks to use, let's reply to them with the same
|
||||
text reversed:
|
||||
@client.on(events.NewMessage)
|
||||
def my_event_handler(event):
|
||||
if 'hello' in event.raw_text:
|
||||
event.reply('hi!')
|
||||
|
||||
client.idle()
|
||||
|
||||
|
||||
Not much, but there might be some things unclear. What does this code do?
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.types import UpdateShortMessage, PeerUser
|
||||
from telethon import TelegramClient, events
|
||||
|
||||
def replier(update):
|
||||
if isinstance(update, UpdateShortMessage) and not update.out:
|
||||
client.send_message(PeerUser(update.user_id), update.message[::-1])
|
||||
client = TelegramClient(..., update_workers=1, spawn_read_thread=False)
|
||||
client.start()
|
||||
|
||||
|
||||
client.add_update_handler(replier)
|
||||
input('Press enter to stop this!')
|
||||
client.disconnect()
|
||||
|
||||
We only ask you one thing: don't keep this running for too long, or your
|
||||
contacts will go mad.
|
||||
|
||||
|
||||
Spawning no worker at all
|
||||
*************************
|
||||
|
||||
All the workers do is loop forever and poll updates from a queue that is
|
||||
filled from the ``ReadThread``, responsible for reading every item off
|
||||
the network. If you only need a worker and the ``MainThread`` would be
|
||||
doing no other job, this is the preferred way. You can easily do the same
|
||||
as the workers like so:
|
||||
This is normal initialization (of course, pass session name, API ID and hash).
|
||||
Nothing we don't know already.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
while True:
|
||||
try:
|
||||
update = client.updates.poll()
|
||||
if not update:
|
||||
continue
|
||||
|
||||
print('I received', update)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
client.disconnect()
|
||||
|
||||
Note that ``poll`` accepts a ``timeout=`` parameter, and it will return
|
||||
``None`` if other thread got the update before you could or if the timeout
|
||||
expired, so it's important to check ``if not update``.
|
||||
|
||||
This can coexist with the rest of ``N`` workers, or you can set it to ``0``
|
||||
additional workers:
|
||||
|
||||
``client = TelegramClient('session', api_id, api_hash, update_workers=0)``
|
||||
|
||||
You **must** set it to ``0`` (or other number), as it defaults to ``None``
|
||||
and there is a different. ``None`` workers means updates won't be processed
|
||||
*at all*, so you must set it to some value (``0`` or greater) if you want
|
||||
``client.updates.poll()`` to work.
|
||||
@client.on(events.NewMessage)
|
||||
|
||||
|
||||
Using the main thread instead the ``ReadThread``
|
||||
************************************************
|
||||
|
||||
If you have no work to do on the ``MainThread`` and you were planning to have
|
||||
a ``while True: sleep(1)``, don't do that. Instead, don't spawn the secondary
|
||||
``ReadThread`` at all like so:
|
||||
This Python decorator will attach itself to the ``my_event_handler``
|
||||
definition, and basically means that *on* a ``NewMessage`` *event*,
|
||||
the callback function you're about to define will be called:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client = TelegramClient(
|
||||
...
|
||||
spawn_read_thread=False
|
||||
)
|
||||
def my_event_handler(event):
|
||||
if 'hello' in event.raw_text:
|
||||
event.reply('hi!')
|
||||
|
||||
And then ``.idle()`` from the ``MainThread``:
|
||||
|
||||
``client.idle()``
|
||||
|
||||
You can stop it with :kbd:`Control+C`, and you can configure the signals
|
||||
to be used in a similar fashion to `Python Telegram Bot`__.
|
||||
|
||||
As a complete example:
|
||||
If a ``NewMessage`` event occurs, and ``'hello'`` is in the text of the
|
||||
message, we ``reply`` to the event with a ``'hi!'`` message.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def callback(update):
|
||||
print('I received', update)
|
||||
|
||||
client = TelegramClient('session', api_id, api_hash,
|
||||
update_workers=1, spawn_read_thread=False)
|
||||
|
||||
client.connect()
|
||||
client.add_update_handler(callback)
|
||||
client.idle() # ends with Ctrl+C
|
||||
client.disconnect()
|
||||
client.idle()
|
||||
|
||||
|
||||
Finally, this tells the client that we're done with our code, and want
|
||||
to listen for all these events to occur. Of course, you might want to
|
||||
do other things instead idling. For this refer to :ref:`update-modes`.
|
||||
|
||||
|
||||
More on events
|
||||
**************
|
||||
|
||||
The ``NewMessage`` event has much more than what was shown. You can access
|
||||
the ``.sender`` of the message through that member, or even see if the message
|
||||
had ``.media``, a ``.photo`` or a ``.document`` (which you could download with
|
||||
for example ``client.download_media(event.photo)``.
|
||||
|
||||
If you don't want to ``.reply`` as a reply, you can use the ``.respond()``
|
||||
method instead. Of course, there are more events such as ``ChatAction`` or
|
||||
``UserUpdate``, and they're all used in the same way. Simply add the
|
||||
``@client.on(events.XYZ)`` decorator on the top of your handler and you're
|
||||
done! The event that will be passed always is of type ``XYZ.Event`` (for
|
||||
instance, ``NewMessage.Event``), except for the ``Raw`` event which just
|
||||
passes the ``Update`` object.
|
||||
|
||||
You can put the same event on many handlers, and even different events on
|
||||
the same handler. You can also have a handler work on only specific chats,
|
||||
for example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ast
|
||||
import random
|
||||
|
||||
|
||||
@client.on(events.NewMessage(chats='TelethonOffTopic', incoming=True))
|
||||
def normal_handler(event):
|
||||
if 'roll' in event.raw_text:
|
||||
event.reply(str(random.randint(1, 6)))
|
||||
|
||||
|
||||
@client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True))
|
||||
def admin_handler(event):
|
||||
if event.raw_text.startswith('eval'):
|
||||
expression = event.raw_text.replace('eval', '').strip()
|
||||
event.reply(str(ast.literal_eval(expression)))
|
||||
|
||||
|
||||
You can pass one or more chats to the ``chats`` parameter (as a list or tuple),
|
||||
and only events from there will be processed. You can also specify whether you
|
||||
want to handle incoming or outgoing messages (those you receive or those you
|
||||
send). In this example, people can say ``'roll'`` and you will reply with a
|
||||
random number, while if you say ``'eval 4+4'``, you will reply with the
|
||||
solution. Try it!
|
||||
|
||||
|
||||
Events module
|
||||
*************
|
||||
|
||||
.. automodule:: telethon.events
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
__ https://python-telegram-bot.org/
|
||||
__ https://lonamiwebs.github.io/Telethon/types/update.html
|
||||
__ https://github.com/python-telegram-bot/python-telegram-bot/blob/4b3315db6feebafb94edcaa803df52bb49999ced/telegram/ext/updater.py#L460
|
||||
|
|
|
@ -49,6 +49,7 @@ heavy job for you, so you can focus on developing an application.
|
|||
|
||||
extra/advanced-usage/accessing-the-full-api
|
||||
extra/advanced-usage/sessions
|
||||
extra/advanced-usage/update-modes
|
||||
|
||||
|
||||
.. _Examples:
|
||||
|
|
4
readthedocs/telethon.events.rst
Normal file
4
readthedocs/telethon.events.rst
Normal file
|
@ -0,0 +1,4 @@
|
|||
telethon\.events package
|
||||
========================
|
||||
|
||||
|
|
@ -26,6 +26,14 @@ telethon\.telegram\_client module
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
telethon\.events package
|
||||
------------------------
|
||||
|
||||
.. toctree::
|
||||
|
||||
telethon.events
|
||||
|
||||
|
||||
telethon\.update\_state module
|
||||
------------------------------
|
||||
|
||||
|
|
868
telethon/events/__init__.py
Normal file
868
telethon/events/__init__.py
Normal file
|
@ -0,0 +1,868 @@
|
|||
import abc
|
||||
import datetime
|
||||
import itertools
|
||||
|
||||
from .. import utils
|
||||
from ..errors import RPCError
|
||||
from ..extensions import markdown
|
||||
from ..tl import types, functions
|
||||
|
||||
|
||||
class _EventBuilder(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def build(self, update):
|
||||
"""Builds an event for the given update if possible, or returns None"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def resolve(self, client):
|
||||
"""Helper method to allow event builders to be resolved before usage"""
|
||||
|
||||
|
||||
class _EventCommon(abc.ABC):
|
||||
"""Intermediate class with common things to all events"""
|
||||
|
||||
def __init__(self, chat_peer=None, msg_id=None, broadcast=False):
|
||||
self._client = None
|
||||
self._chat_peer = chat_peer
|
||||
self._message_id = msg_id
|
||||
self._input_chat = None
|
||||
self._chat = None
|
||||
|
||||
self.is_private = isinstance(chat_peer, types.PeerUser)
|
||||
self.is_group = (
|
||||
isinstance(chat_peer, (types.PeerChat, types.PeerChannel))
|
||||
and not broadcast
|
||||
)
|
||||
self.is_channel = isinstance(chat_peer, types.PeerChannel)
|
||||
|
||||
def _get_input_entity(self, msg_id, entity_id, chat=None):
|
||||
"""
|
||||
Helper function to call GetMessages on the give msg_id and
|
||||
return the input entity whose ID is the given entity ID.
|
||||
|
||||
If ``chat`` is present it must be an InputPeer.
|
||||
"""
|
||||
try:
|
||||
if isinstance(chat, types.InputPeerChannel):
|
||||
result = self._client(
|
||||
functions.channels.GetMessagesRequest(chat, [msg_id])
|
||||
)
|
||||
else:
|
||||
result = self._client(
|
||||
functions.messages.GetMessagesRequest([msg_id])
|
||||
)
|
||||
except RPCError:
|
||||
return
|
||||
entity = {
|
||||
utils.get_peer_id(x): x for x in itertools.chain(
|
||||
getattr(result, 'chats', []),
|
||||
getattr(result, 'users', []))
|
||||
}.get(entity_id)
|
||||
if entity:
|
||||
return utils.get_input_peer(entity)
|
||||
|
||||
@property
|
||||
def input_chat(self):
|
||||
"""
|
||||
The (:obj:`InputPeer`) (group, megagroup or channel) on which
|
||||
the event occurred. This doesn't have the title or anything,
|
||||
but is useful if you don't need those to avoid further
|
||||
requests.
|
||||
|
||||
Note that this might be ``None`` if the library can't find it.
|
||||
"""
|
||||
|
||||
if self._input_chat is None and self._chat_peer is not None:
|
||||
try:
|
||||
self._input_chat = self._client.get_input_entity(
|
||||
self._chat_peer
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
# The library hasn't seen this chat, get the message
|
||||
if not isinstance(self._chat_peer, types.PeerChannel):
|
||||
# TODO For channels, getDifference? Maybe looking
|
||||
# in the dialogs (which is already done) is enough.
|
||||
if self._message_id is not None:
|
||||
self._input_chat = self._get_input_entity(
|
||||
self._message_id,
|
||||
utils.get_peer_id(self._chat_peer)
|
||||
)
|
||||
return self._input_chat
|
||||
|
||||
@property
|
||||
def chat(self):
|
||||
"""
|
||||
The (:obj:`User` | :obj:`Chat` | :obj:`Channel`, optional) on which
|
||||
the event occurred. This property will make an API call the first time
|
||||
to get the most up to date version of the chat, so use with care as
|
||||
there is no caching besides local caching yet.
|
||||
"""
|
||||
if self._chat is None and self.input_chat:
|
||||
self._chat = self._client.get_entity(self._input_chat)
|
||||
return self._chat
|
||||
|
||||
|
||||
class Raw(_EventBuilder):
|
||||
"""
|
||||
Represents a raw event. The event is the update itself.
|
||||
"""
|
||||
def resolve(self, client):
|
||||
pass
|
||||
|
||||
def build(self, update):
|
||||
return update
|
||||
|
||||
|
||||
# Classes defined here are actually Event builders
|
||||
# for their inner Event classes. Inner ._client is
|
||||
# set later by the creator TelegramClient.
|
||||
class NewMessage(_EventBuilder):
|
||||
"""
|
||||
Represents a new message event builder.
|
||||
|
||||
Args:
|
||||
incoming (:obj:`bool`, optional):
|
||||
If set to ``True``, only **incoming** messages will be handled.
|
||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
||||
|
||||
outgoing (:obj:`bool`, optional):
|
||||
If set to ``True``, only **outgoing** messages will be handled.
|
||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||
|
||||
chats (:obj:`entity`, optional):
|
||||
May be one or more entities (username/peer/etc.). By default,
|
||||
only matching chats will be handled.
|
||||
|
||||
blacklist_chats (:obj:`bool`, optional):
|
||||
Whether to treat the the list of chats as a blacklist (if
|
||||
it matches it will NOT be handled) or a whitelist (default).
|
||||
"""
|
||||
def __init__(self, incoming=None, outgoing=None,
|
||||
chats=None, blacklist_chats=False):
|
||||
if incoming and outgoing:
|
||||
raise ValueError('Can only set either incoming or outgoing')
|
||||
|
||||
self.incoming = incoming
|
||||
self.outgoing = outgoing
|
||||
self.chats = chats
|
||||
self.blacklist_chats = blacklist_chats
|
||||
|
||||
def resolve(self, client):
|
||||
if hasattr(self.chats, '__iter__') and not isinstance(self.chats, str):
|
||||
self.chats = set(utils.get_peer_id(x)
|
||||
for x in client.get_input_entity(self.chats))
|
||||
elif self.chats is not None:
|
||||
self.chats = {utils.get_peer_id(
|
||||
client.get_input_entity(self.chats))}
|
||||
|
||||
def build(self, update):
|
||||
if isinstance(update,
|
||||
(types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||
if not isinstance(update.message, types.Message):
|
||||
return # We don't care about MessageService's here
|
||||
event = NewMessage.Event(update.message)
|
||||
elif isinstance(update, types.UpdateShortMessage):
|
||||
event = NewMessage.Event(types.Message(
|
||||
out=update.out,
|
||||
mentioned=update.mentioned,
|
||||
media_unread=update.media_unread,
|
||||
silent=update.silent,
|
||||
id=update.id,
|
||||
to_id=types.PeerUser(update.user_id),
|
||||
message=update.message,
|
||||
date=update.date,
|
||||
fwd_from=update.fwd_from,
|
||||
via_bot_id=update.via_bot_id,
|
||||
reply_to_msg_id=update.reply_to_msg_id,
|
||||
entities=update.entities
|
||||
))
|
||||
else:
|
||||
return
|
||||
|
||||
# Short-circuit if we let pass all events
|
||||
if all(x is None for x in (self.incoming, self.outgoing, self.chats)):
|
||||
return event
|
||||
|
||||
if self.incoming and event.message.out:
|
||||
return
|
||||
if self.outgoing and not event.message.out:
|
||||
return
|
||||
|
||||
if self.chats is not None:
|
||||
inside = utils.get_peer_id(event.message.to_id) in self.chats
|
||||
if inside == self.blacklist_chats:
|
||||
# If this chat matches but it's a blacklist ignore.
|
||||
# If it doesn't match but it's a whitelist ignore.
|
||||
return
|
||||
|
||||
# Tests passed so return the event
|
||||
return event
|
||||
|
||||
class Event(_EventCommon):
|
||||
"""
|
||||
Represents the event of a new message.
|
||||
|
||||
Members:
|
||||
message (:obj:`Message`):
|
||||
This is the original ``Message`` object.
|
||||
|
||||
is_private (:obj:`bool`):
|
||||
True if the message was sent as a private message.
|
||||
|
||||
is_group (:obj:`bool`):
|
||||
True if the message was sent on a group or megagroup.
|
||||
|
||||
is_channel (:obj:`bool`):
|
||||
True if the message was sent on a megagroup or channel.
|
||||
|
||||
is_reply (:obj:`str`):
|
||||
Whether the message is a reply to some other or not.
|
||||
"""
|
||||
def __init__(self, message):
|
||||
super().__init__(chat_peer=message.to_id,
|
||||
msg_id=message.id, broadcast=bool(message.post))
|
||||
|
||||
self.message = message
|
||||
self._text = None
|
||||
|
||||
self._input_chat = None
|
||||
self._chat = None
|
||||
self._input_sender = None
|
||||
self._sender = None
|
||||
|
||||
self.is_reply = bool(message.reply_to_msg_id)
|
||||
self._reply_message = None
|
||||
|
||||
def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the message (not as a reply). This is a shorthand for
|
||||
``client.send_message(event.chat, ...)``.
|
||||
"""
|
||||
return self._client.send_message(self.input_chat, *args, **kwargs)
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the message (as a reply). This is a shorthand for
|
||||
``client.send_message(event.chat, ..., reply_to=event.message.id)``.
|
||||
"""
|
||||
return self._client.send_message(self.input_chat,
|
||||
reply_to=self.message.id,
|
||||
*args, **kwargs)
|
||||
|
||||
@property
|
||||
def input_sender(self):
|
||||
"""
|
||||
This (:obj:`InputPeer`) is the input version of the user who
|
||||
sent the message. Similarly to ``input_chat``, this doesn't have
|
||||
things like username or similar, but still useful in some cases.
|
||||
|
||||
Note that this might not be available if the library can't
|
||||
find the input chat.
|
||||
"""
|
||||
if self._input_sender is None:
|
||||
try:
|
||||
self._input_sender = self._client.get_input_entity(
|
||||
self.message.from_id
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
if isinstance(self.message.to_id, types.PeerChannel):
|
||||
# We can rely on self.input_chat for this
|
||||
self._input_sender = self._get_input_entity(
|
||||
self.message.id,
|
||||
self.message.from_id,
|
||||
chat=self.input_chat
|
||||
)
|
||||
|
||||
return self._input_sender
|
||||
|
||||
@property
|
||||
def sender(self):
|
||||
"""
|
||||
This (:obj:`User`) will make an API call the first time to get
|
||||
the most up to date version of the sender, so use with care as
|
||||
there is no caching besides local caching yet.
|
||||
|
||||
``input_sender`` needs to be available (often the case).
|
||||
"""
|
||||
if self._sender is None and self.input_sender:
|
||||
self._sender = self._client.get_entity(self._input_sender)
|
||||
return self._sender
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The message text, markdown-formatted.
|
||||
"""
|
||||
if self._text is None:
|
||||
if not self.message.entities:
|
||||
return self.message.message
|
||||
self._text = markdown.unparse(self.message.message,
|
||||
self.message.entities or [])
|
||||
return self._text
|
||||
|
||||
@property
|
||||
def raw_text(self):
|
||||
"""
|
||||
The raw message text, ignoring any formatting.
|
||||
"""
|
||||
return self.message.message
|
||||
|
||||
@property
|
||||
def reply_message(self):
|
||||
"""
|
||||
This (:obj:`Message`, optional) will make an API call the first
|
||||
time to get the full ``Message`` object that one was replying to,
|
||||
so use with care as there is no caching besides local caching yet.
|
||||
"""
|
||||
if not self.message.reply_to_msg_id:
|
||||
return None
|
||||
|
||||
if self._reply_message is None:
|
||||
if isinstance(self.input_chat, types.InputPeerChannel):
|
||||
r = self._client(functions.channels.GetMessagesRequest(
|
||||
self.input_chat, [self.message.reply_to_msg_id]
|
||||
))
|
||||
else:
|
||||
r = self._client(functions.messages.GetMessagesRequest(
|
||||
[self.message.reply_to_msg_id]
|
||||
))
|
||||
if not isinstance(r, types.messages.MessagesNotModified):
|
||||
self._reply_message = r.messages[0]
|
||||
|
||||
return self._reply_message
|
||||
|
||||
@property
|
||||
def forward(self):
|
||||
"""
|
||||
The unmodified (:obj:`MessageFwdHeader`, optional).
|
||||
"""
|
||||
return self.message.fwd_from
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
"""
|
||||
The unmodified (:obj:`MessageMedia`, optional).
|
||||
"""
|
||||
return self.message.media
|
||||
|
||||
@property
|
||||
def photo(self):
|
||||
"""
|
||||
If the message media is a photo,
|
||||
this returns the (:obj:`Photo`) object.
|
||||
"""
|
||||
if isinstance(self.message.media, types.MessageMediaPhoto):
|
||||
photo = self.message.media.photo
|
||||
if isinstance(photo, types.Photo):
|
||||
return photo
|
||||
|
||||
@property
|
||||
def document(self):
|
||||
"""
|
||||
If the message media is a document,
|
||||
this returns the (:obj:`Document`) object.
|
||||
"""
|
||||
if isinstance(self.message.media, types.MessageMediaDocument):
|
||||
doc = self.message.media.document
|
||||
if isinstance(doc, types.Document):
|
||||
return doc
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
"""
|
||||
Whether the message is outgoing (i.e. you sent it from
|
||||
another session) or incoming (i.e. someone else sent it).
|
||||
"""
|
||||
return self.message.out
|
||||
|
||||
|
||||
class ChatAction(_EventBuilder):
|
||||
"""
|
||||
Represents an action in a chat (such as user joined, left, or new pin).
|
||||
|
||||
Args:
|
||||
chats (:obj:`entity`, optional):
|
||||
May be one or more entities (username/peer/etc.). By default,
|
||||
only matching chats will be handled.
|
||||
|
||||
blacklist_chats (:obj:`bool`, optional):
|
||||
Whether to treat the the list of chats as a blacklist (if
|
||||
it matches it will NOT be handled) or a whitelist (default).
|
||||
|
||||
"""
|
||||
def __init__(self, chats=None, blacklist_chats=False):
|
||||
# TODO This can probably be reused in all builders
|
||||
self.chats = chats
|
||||
self.blacklist_chats = blacklist_chats
|
||||
|
||||
def resolve(self, client):
|
||||
if hasattr(self.chats, '__iter__') and not isinstance(self.chats, str):
|
||||
self.chats = set(utils.get_peer_id(x)
|
||||
for x in client.get_input_entity(self.chats))
|
||||
elif self.chats is not None:
|
||||
self.chats = {utils.get_peer_id(
|
||||
client.get_input_entity(self.chats))}
|
||||
|
||||
def build(self, update):
|
||||
if isinstance(update, types.UpdateChannelPinnedMessage):
|
||||
# Telegram sends UpdateChannelPinnedMessage and then
|
||||
# UpdateNewChannelMessage with MessageActionPinMessage.
|
||||
event = ChatAction.Event(types.PeerChannel(update.channel_id),
|
||||
new_pin=update.id)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantAdd):
|
||||
event = ChatAction.Event(types.PeerChat(update.chat_id),
|
||||
added_by=update.inviter_id or True,
|
||||
users=update.user_id)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantDelete):
|
||||
event = ChatAction.Event(types.PeerChat(update.chat_id),
|
||||
kicked_by=True,
|
||||
users=update.user_id)
|
||||
|
||||
elif (isinstance(update, (
|
||||
types.UpdateNewMessage, types.UpdateNewChannelMessage))
|
||||
and isinstance(update.message, types.MessageService)):
|
||||
msg = update.message
|
||||
action = update.message.action
|
||||
if isinstance(action, types.MessageActionChatJoinedByLink):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
added_by=True,
|
||||
users=msg.from_id)
|
||||
elif isinstance(action, types.MessageActionChatAddUser):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
added_by=msg.from_id or True,
|
||||
users=action.users)
|
||||
elif isinstance(action, types.MessageActionChatDeleteUser):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
kicked_by=msg.from_id or True,
|
||||
users=action.user_id)
|
||||
elif isinstance(action, types.MessageActionChatCreate):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
users=action.users,
|
||||
created=True,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChannelCreate):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
created=True,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
new_photo=action.photo)
|
||||
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
new_photo=True)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
if self.chats is None:
|
||||
return event
|
||||
else:
|
||||
inside = utils.get_peer_id(event._chat_peer) in self.chats
|
||||
if inside == self.blacklist_chats:
|
||||
# If this chat matches but it's a blacklist ignore.
|
||||
# If it doesn't match but it's a whitelist ignore.
|
||||
return
|
||||
|
||||
return event
|
||||
|
||||
class Event(_EventCommon):
|
||||
"""
|
||||
Represents the event of a new chat action.
|
||||
|
||||
Members:
|
||||
new_pin (:obj:`bool`):
|
||||
``True`` if the pin has changed (new pin or removed).
|
||||
|
||||
new_photo (:obj:`bool`):
|
||||
``True`` if there's a new chat photo (or it was removed).
|
||||
|
||||
photo (:obj:`Photo`, optional):
|
||||
The new photo (or ``None`` if it was removed).
|
||||
|
||||
|
||||
user_added (:obj:`bool`):
|
||||
``True`` if the user was added by some other.
|
||||
|
||||
user_joined (:obj:`bool`):
|
||||
``True`` if the user joined on their own.
|
||||
|
||||
user_left (:obj:`bool`):
|
||||
``True`` if the user left on their own.
|
||||
|
||||
user_kicked (:obj:`bool`):
|
||||
``True`` if the user was kicked by some other.
|
||||
|
||||
created (:obj:`bool`, optional):
|
||||
``True`` if this chat was just created.
|
||||
|
||||
new_title (:obj:`bool`, optional):
|
||||
The new title string for the chat, if applicable.
|
||||
"""
|
||||
def __init__(self, chat_peer, new_pin=None, new_photo=None,
|
||||
added_by=None, kicked_by=None, created=None,
|
||||
users=None, new_title=None):
|
||||
super().__init__(chat_peer=chat_peer, msg_id=new_pin)
|
||||
|
||||
self.new_pin = isinstance(new_pin, int)
|
||||
self._pinned_message = new_pin
|
||||
|
||||
self.new_photo = new_photo is not None
|
||||
self.photo = \
|
||||
new_photo if isinstance(new_photo, types.Photo) else None
|
||||
|
||||
self._added_by = None
|
||||
self._kicked_by = None
|
||||
self.user_added, self.user_joined, self.user_left,\
|
||||
self.user_kicked = (False, False, False, False)
|
||||
|
||||
if added_by is True:
|
||||
self.user_joined = True
|
||||
elif added_by:
|
||||
self.user_added = True
|
||||
self._added_by = added_by
|
||||
|
||||
if kicked_by is True:
|
||||
self.user_left = True
|
||||
elif kicked_by:
|
||||
self.user_kicked = True
|
||||
self._kicked_by = kicked_by
|
||||
|
||||
self.created = bool(created)
|
||||
self._user_peers = users if isinstance(users, list) else [users]
|
||||
self._users = None
|
||||
self.new_title = new_title
|
||||
|
||||
@property
|
||||
def pinned_message(self):
|
||||
"""
|
||||
If ``new_pin`` is ``True``, this returns the (:obj:`Message`)
|
||||
object that was pinned.
|
||||
"""
|
||||
if self._pinned_message == 0:
|
||||
return None
|
||||
|
||||
if isinstance(self._pinned_message, int) and self.input_chat:
|
||||
r = self._client(functions.channels.GetMessagesRequest(
|
||||
self._input_chat, [self._pinned_message]
|
||||
))
|
||||
try:
|
||||
self._pinned_message = next(
|
||||
x for x in r.messages
|
||||
if isinstance(x, types.Message)
|
||||
and x.id == self._pinned_message
|
||||
)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
if isinstance(self._pinned_message, types.Message):
|
||||
return self._pinned_message
|
||||
|
||||
@property
|
||||
def added_by(self):
|
||||
"""
|
||||
The user who added ``users``, if applicable (``None`` otherwise).
|
||||
"""
|
||||
if self._added_by and not isinstance(self._added_by, types.User):
|
||||
self._added_by = self._client.get_entity(self._added_by)
|
||||
return self._added_by
|
||||
|
||||
@property
|
||||
def kicked_by(self):
|
||||
"""
|
||||
The user who kicked ``users``, if applicable (``None`` otherwise).
|
||||
"""
|
||||
if self._kicked_by and not isinstance(self._kicked_by, types.User):
|
||||
self._kicked_by = self._client.get_entity(self._kicked_by)
|
||||
return self._kicked_by
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""
|
||||
The single user that takes part in this action (e.g. joined).
|
||||
|
||||
Might be ``None`` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
try:
|
||||
return next(self.users)
|
||||
except (StopIteration, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
"""
|
||||
A list of users that take part in this action (e.g. joined).
|
||||
|
||||
Might be empty if the information can't be retrieved or there
|
||||
are no users taking part.
|
||||
"""
|
||||
if self._users is None and self._user_peers:
|
||||
try:
|
||||
self._users = self._client.get_entity(self._user_peers)
|
||||
except (TypeError, ValueError):
|
||||
self._users = []
|
||||
|
||||
return self._users
|
||||
|
||||
|
||||
class UserUpdate(_EventBuilder):
|
||||
"""
|
||||
Represents an user update (gone online, offline, joined Telegram).
|
||||
"""
|
||||
|
||||
def build(self, update):
|
||||
if isinstance(update, types.UpdateUserStatus):
|
||||
event = UserUpdate.Event(update.user_id,
|
||||
status=update.status)
|
||||
else:
|
||||
return
|
||||
|
||||
return event
|
||||
|
||||
def resolve(self, client):
|
||||
pass
|
||||
|
||||
class Event(_EventCommon):
|
||||
"""
|
||||
Represents the event of an user status update (last seen, joined).
|
||||
|
||||
Members:
|
||||
online (:obj:`bool`, optional):
|
||||
``True`` if the user is currently online, ``False`` otherwise.
|
||||
Might be ``None`` if this information is not present.
|
||||
|
||||
last_seen (:obj:`datetime`, optional):
|
||||
Exact date when the user was last seen if known.
|
||||
|
||||
until (:obj:`datetime`, optional):
|
||||
Until when will the user remain online.
|
||||
|
||||
within_months (:obj:`bool`):
|
||||
``True`` if the user was seen within 30 days.
|
||||
|
||||
within_weeks (:obj:`bool`):
|
||||
``True`` if the user was seen within 7 days.
|
||||
|
||||
recently (:obj:`bool`):
|
||||
``True`` if the user was seen within a day.
|
||||
|
||||
action (:obj:`SendMessageAction`, optional):
|
||||
The "typing" action if any the user is performing if any.
|
||||
|
||||
cancel (:obj:`bool`):
|
||||
``True`` if the action was cancelling other actions.
|
||||
|
||||
typing (:obj:`bool`):
|
||||
``True`` if the action is typing a message.
|
||||
|
||||
recording (:obj:`bool`):
|
||||
``True`` if the action is recording something.
|
||||
|
||||
uploading (:obj:`bool`):
|
||||
``True`` if the action is uploading something.
|
||||
|
||||
playing (:obj:`bool`):
|
||||
``True`` if the action is playing a game.
|
||||
|
||||
audio (:obj:`bool`):
|
||||
``True`` if what's being recorded/uploaded is an audio.
|
||||
|
||||
round (:obj:`bool`):
|
||||
``True`` if what's being recorded/uploaded is a round video.
|
||||
|
||||
video (:obj:`bool`):
|
||||
``True`` if what's being recorded/uploaded is an video.
|
||||
|
||||
document (:obj:`bool`):
|
||||
``True`` if what's being uploaded is document.
|
||||
|
||||
geo (:obj:`bool`):
|
||||
``True`` if what's being uploaded is a geo.
|
||||
|
||||
photo (:obj:`bool`):
|
||||
``True`` if what's being uploaded is a photo.
|
||||
|
||||
contact (:obj:`bool`):
|
||||
``True`` if what's being uploaded (selected) is a contact.
|
||||
"""
|
||||
def __init__(self, user_id, status=None, typing=None):
|
||||
super().__init__(types.PeerUser(user_id))
|
||||
|
||||
self.online = None if status is None else \
|
||||
isinstance(status, types.UserStatusOnline)
|
||||
|
||||
self.last_seen = status.was_online if \
|
||||
isinstance(status, types.UserStatusOffline) else None
|
||||
|
||||
self.until = status.expires if \
|
||||
isinstance(status, types.UserStatusOnline) else None
|
||||
|
||||
if self.last_seen:
|
||||
diff = datetime.datetime.now() - self.last_seen
|
||||
if diff < datetime.timedelta(days=30):
|
||||
self.within_months = True
|
||||
if diff < datetime.timedelta(days=7):
|
||||
self.within_weeks = True
|
||||
if diff < datetime.timedelta(days=1):
|
||||
self.recently = True
|
||||
else:
|
||||
self.within_months = self.within_weeks = self.recently = False
|
||||
if isinstance(status, (types.UserStatusOnline,
|
||||
types.UserStatusRecently)):
|
||||
self.within_months = self.within_weeks = True
|
||||
self.recently = True
|
||||
elif isinstance(status, types.UserStatusLastWeek):
|
||||
self.within_months = self.within_weeks = True
|
||||
elif isinstance(status, types.UserStatusLastMonth):
|
||||
self.within_months = True
|
||||
|
||||
self.action = typing
|
||||
if typing:
|
||||
self.cancel = self.typing = self.recording = self.uploading = \
|
||||
self.playing = False
|
||||
self.audio = self.round = self.video = self.document = \
|
||||
self.geo = self.photo = self.contact = False
|
||||
|
||||
if isinstance(typing, types.SendMessageCancelAction):
|
||||
self.cancel = True
|
||||
elif isinstance(typing, types.SendMessageTypingAction):
|
||||
self.typing = True
|
||||
elif isinstance(typing, types.SendMessageGamePlayAction):
|
||||
self.playing = True
|
||||
elif isinstance(typing, types.SendMessageGeoLocationAction):
|
||||
self.geo = True
|
||||
elif isinstance(typing, types.SendMessageRecordAudioAction):
|
||||
self.recording = self.audio = True
|
||||
elif isinstance(typing, types.SendMessageRecordRoundAction):
|
||||
self.recording = self.round = True
|
||||
elif isinstance(typing, types.SendMessageRecordVideoAction):
|
||||
self.recording = self.video = True
|
||||
elif isinstance(typing, types.SendMessageChooseContactAction):
|
||||
self.uploading = self.contact = True
|
||||
elif isinstance(typing, types.SendMessageUploadAudioAction):
|
||||
self.uploading = self.audio = True
|
||||
elif isinstance(typing, types.SendMessageUploadDocumentAction):
|
||||
self.uploading = self.document = True
|
||||
elif isinstance(typing, types.SendMessageUploadPhotoAction):
|
||||
self.uploading = self.photo = True
|
||||
elif isinstance(typing, types.SendMessageUploadRoundAction):
|
||||
self.uploading = self.round = True
|
||||
elif isinstance(typing, types.SendMessageUploadVideoAction):
|
||||
self.uploading = self.video = True
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""Alias around the chat (conversation)."""
|
||||
return self.chat
|
||||
|
||||
|
||||
class MessageChanged(_EventBuilder):
|
||||
"""
|
||||
Represents a message changed (edited or deleted).
|
||||
"""
|
||||
|
||||
def build(self, update):
|
||||
if isinstance(update, (types.UpdateEditMessage,
|
||||
types.UpdateEditChannelMessage)):
|
||||
event = MessageChanged.Event(edit_msg=update.message)
|
||||
elif isinstance(update, (types.UpdateDeleteMessages,
|
||||
types.UpdateDeleteChannelMessages)):
|
||||
event = MessageChanged.Event(
|
||||
deleted_ids=update.messages,
|
||||
peer=types.PeerChannel(update.channel_id)
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
return event
|
||||
|
||||
def resolve(self, client):
|
||||
pass
|
||||
|
||||
class Event(_EventCommon):
|
||||
"""
|
||||
Represents the event of an user status update (last seen, joined).
|
||||
|
||||
Members:
|
||||
edited (:obj:`bool`):
|
||||
``True`` if the message was edited.
|
||||
|
||||
message (:obj:`Message`, optional):
|
||||
The new edited message, if any.
|
||||
|
||||
deleted (:obj:`bool`):
|
||||
``True`` if the message IDs were deleted.
|
||||
|
||||
deleted_ids (:obj:`List[int]`):
|
||||
A list containing the IDs of the messages that were deleted.
|
||||
|
||||
input_sender (:obj:`InputPeer`):
|
||||
This is the input version of the user who edited the message.
|
||||
Similarly to ``input_chat``, this doesn't have things like
|
||||
username or similar, but still useful in some cases.
|
||||
|
||||
Note that this might not be available if the library can't
|
||||
find the input chat.
|
||||
|
||||
sender (:obj:`User`):
|
||||
This property will make an API call the first time to get the
|
||||
most up to date version of the sender, so use with care as
|
||||
there is no caching besides local caching yet.
|
||||
|
||||
``input_sender`` needs to be available (often the case).
|
||||
"""
|
||||
def __init__(self, edit_msg=None, deleted_ids=None, peer=None):
|
||||
super().__init__(peer if not edit_msg else edit_msg.to_id)
|
||||
|
||||
self.edited = bool(edit_msg)
|
||||
self.message = edit_msg
|
||||
self.deleted = bool(deleted_ids)
|
||||
self.deleted_ids = deleted_ids or []
|
||||
self._input_sender = None
|
||||
self._sender = None
|
||||
|
||||
@property
|
||||
def input_sender(self):
|
||||
"""
|
||||
This (:obj:`InputPeer`) is the input version of the user who
|
||||
sent the message. Similarly to ``input_chat``, this doesn't have
|
||||
things like username or similar, but still useful in some cases.
|
||||
|
||||
Note that this might not be available if the library can't
|
||||
find the input chat.
|
||||
"""
|
||||
# TODO Code duplication
|
||||
if self._input_sender is None and self.message:
|
||||
try:
|
||||
self._input_sender = self._client.get_input_entity(
|
||||
self.message.from_id
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
if isinstance(self.message.to_id, types.PeerChannel):
|
||||
# We can rely on self.input_chat for this
|
||||
self._input_sender = self._get_input_entity(
|
||||
self.message.id,
|
||||
self.message.from_id,
|
||||
chat=self.input_chat
|
||||
)
|
||||
|
||||
return self._input_sender
|
||||
|
||||
@property
|
||||
def sender(self):
|
||||
"""
|
||||
This (:obj:`User`) will make an API call the first time to get
|
||||
the most up to date version of the sender, so use with care as
|
||||
there is no caching besides local caching yet.
|
||||
|
||||
``input_sender`` needs to be available (often the case).
|
||||
"""
|
||||
if self._sender is None and self.input_sender:
|
||||
self._sender = self._client.get_entity(self._input_sender)
|
||||
return self._sender
|
|
@ -160,6 +160,8 @@ class TelegramClient(TelegramBareClient):
|
|||
**kwargs
|
||||
)
|
||||
|
||||
self._event_builders = []
|
||||
|
||||
# Some fields to easy signing in. Let {phone: hash} be
|
||||
# a dictionary because the user may change their mind.
|
||||
self._phone_code_hash = {}
|
||||
|
@ -1623,6 +1625,41 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
# endregion
|
||||
|
||||
# region Event handling
|
||||
|
||||
def on(self, event):
|
||||
"""
|
||||
|
||||
Turns the given entity into a valid Telegram user or chat.
|
||||
|
||||
Args:
|
||||
event (:obj:`_EventBuilder` | :obj:`type`):
|
||||
The event builder class or instance to be used,
|
||||
for instance ``events.NewMessage``.
|
||||
"""
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
|
||||
event.resolve(self)
|
||||
|
||||
def decorator(f):
|
||||
self._event_builders.append((event, f))
|
||||
return f
|
||||
|
||||
if self._on_handler not in self.updates.handlers:
|
||||
self.add_update_handler(self._on_handler)
|
||||
|
||||
return decorator
|
||||
|
||||
def _on_handler(self, update):
|
||||
for builder, callback in self._event_builders:
|
||||
event = builder.build(update)
|
||||
if event:
|
||||
event._client = self
|
||||
callback(event)
|
||||
|
||||
# endregion
|
||||
|
||||
# region Small utilities to make users' life easier
|
||||
|
||||
def get_entity(self, entity):
|
||||
|
|
Loading…
Reference in New Issue
Block a user