2018-04-05 21:14:22 +03:00
|
|
|
import re
|
|
|
|
|
2018-06-20 12:49:18 +03:00
|
|
|
from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set
|
2018-08-21 13:14:32 +03:00
|
|
|
from ..tl import types
|
2018-04-05 21:14:22 +03:00
|
|
|
|
|
|
|
|
|
|
|
@name_inner_event
|
|
|
|
class NewMessage(EventBuilder):
|
|
|
|
"""
|
2019-06-11 12:09:22 +03:00
|
|
|
Occurs whenever a new text message or a message with media arrives.
|
2018-04-05 21:14:22 +03:00
|
|
|
|
|
|
|
Args:
|
|
|
|
incoming (`bool`, optional):
|
2019-07-06 13:10:25 +03:00
|
|
|
If set to `True`, only **incoming** messages will be handled.
|
2018-04-05 21:14:22 +03:00
|
|
|
Mutually exclusive with ``outgoing`` (can only set one of either).
|
|
|
|
|
|
|
|
outgoing (`bool`, optional):
|
2019-07-06 13:10:25 +03:00
|
|
|
If set to `True`, only **outgoing** messages will be handled.
|
2018-04-05 21:14:22 +03:00
|
|
|
Mutually exclusive with ``incoming`` (can only set one of either).
|
|
|
|
|
2018-06-20 12:49:18 +03:00
|
|
|
from_users (`entity`, optional):
|
2019-01-12 15:06:14 +03:00
|
|
|
Unlike `chats`, this parameter filters the *senders* of the
|
|
|
|
message. That is, only messages *sent by these users* will be
|
|
|
|
handled. Use `chats` if you want private messages with this/these
|
|
|
|
users. `from_users` lets you filter by messages sent by *one or
|
|
|
|
more* users across the desired chats (doesn't need a list).
|
2018-06-20 12:49:18 +03:00
|
|
|
|
|
|
|
forwards (`bool`, optional):
|
|
|
|
Whether forwarded messages should be handled or not. By default,
|
2019-07-06 13:10:25 +03:00
|
|
|
both forwarded and normal messages are included. If it's `True`
|
|
|
|
*only* forwards will be handled. If it's `False` only messages
|
2018-06-20 12:49:18 +03:00
|
|
|
that are *not* forwards will be handled.
|
|
|
|
|
2018-04-05 21:14:22 +03:00
|
|
|
pattern (`str`, `callable`, `Pattern`, optional):
|
|
|
|
If set, only messages matching this pattern will be handled.
|
|
|
|
You can specify a regex-like string which will be matched
|
2019-07-06 13:10:25 +03:00
|
|
|
against the message, a callable function that returns `True`
|
2018-04-05 21:14:22 +03:00
|
|
|
if a message is acceptable, or a compiled regex pattern.
|
2020-02-20 12:18:26 +03:00
|
|
|
|
|
|
|
Example
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
from telethon import events
|
|
|
|
|
|
|
|
@client.on(events.NewMessage(pattern='(?i)hello.+'))
|
|
|
|
async def handler(event):
|
|
|
|
# Respond whenever someone says "Hello" and something else
|
|
|
|
await event.reply('Hey!')
|
|
|
|
|
|
|
|
@client.on(events.NewMessage(outgoing=True, pattern='!ping'))
|
|
|
|
async def handler(event):
|
|
|
|
# Say "!pong" whenever you send "!ping", then delete both messages
|
|
|
|
m = await event.respond('!pong')
|
|
|
|
await asyncio.sleep(5)
|
|
|
|
await client.delete_messages(event.chat_id, [event.id, m.id])
|
2018-04-05 21:14:22 +03:00
|
|
|
"""
|
2018-09-09 16:48:54 +03:00
|
|
|
def __init__(self, chats=None, *, blacklist_chats=False, func=None,
|
2018-06-20 12:49:18 +03:00
|
|
|
incoming=None, outgoing=None,
|
|
|
|
from_users=None, forwards=None, pattern=None):
|
2018-07-29 14:03:10 +03:00
|
|
|
if incoming and outgoing:
|
|
|
|
incoming = outgoing = None # Same as no filter
|
|
|
|
elif incoming is not None and outgoing is None:
|
2018-05-28 19:25:01 +03:00
|
|
|
outgoing = not incoming
|
|
|
|
elif outgoing is not None and incoming is None:
|
2018-06-02 13:38:03 +03:00
|
|
|
incoming = not outgoing
|
2018-05-28 19:25:01 +03:00
|
|
|
elif all(x is not None and not x for x in (incoming, outgoing)):
|
|
|
|
raise ValueError("Don't create an event handler if you "
|
2019-04-22 17:56:32 +03:00
|
|
|
"don't want neither incoming nor outgoing!")
|
2018-04-05 21:14:22 +03:00
|
|
|
|
2018-09-09 16:48:54 +03:00
|
|
|
super().__init__(chats, blacklist_chats=blacklist_chats, func=func)
|
2018-04-05 21:14:22 +03:00
|
|
|
self.incoming = incoming
|
|
|
|
self.outgoing = outgoing
|
2018-06-20 12:49:18 +03:00
|
|
|
self.from_users = from_users
|
|
|
|
self.forwards = forwards
|
2018-04-05 21:14:22 +03:00
|
|
|
if isinstance(pattern, str):
|
|
|
|
self.pattern = re.compile(pattern).match
|
|
|
|
elif not pattern or callable(pattern):
|
|
|
|
self.pattern = pattern
|
|
|
|
elif hasattr(pattern, 'match') and callable(pattern.match):
|
|
|
|
self.pattern = pattern.match
|
|
|
|
else:
|
|
|
|
raise TypeError('Invalid pattern type given')
|
|
|
|
|
2018-06-20 12:49:18 +03:00
|
|
|
# Should we short-circuit? E.g. perform no check at all
|
|
|
|
self._no_check = all(x is None for x in (
|
2018-06-20 21:03:44 +03:00
|
|
|
self.chats, self.incoming, self.outgoing, self.pattern,
|
2018-09-09 16:48:54 +03:00
|
|
|
self.from_users, self.forwards, self.from_users, self.func
|
2018-06-20 12:49:18 +03:00
|
|
|
))
|
|
|
|
|
2018-08-27 02:19:37 +03:00
|
|
|
async def _resolve(self, client):
|
|
|
|
await super()._resolve(client)
|
|
|
|
self.from_users = await _into_id_set(client, self.from_users)
|
2018-06-20 12:49:18 +03:00
|
|
|
|
2018-07-19 02:47:32 +03:00
|
|
|
@classmethod
|
2019-08-07 01:46:19 +03:00
|
|
|
def build(cls, update, others=None, self_id=None):
|
2018-04-05 21:14:22 +03:00
|
|
|
if isinstance(update,
|
|
|
|
(types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
if not isinstance(update.message, types.Message):
|
|
|
|
return # We don't care about MessageService's here
|
2018-07-19 02:47:32 +03:00
|
|
|
event = cls.Event(update.message)
|
2018-04-05 21:14:22 +03:00
|
|
|
elif isinstance(update, types.UpdateShortMessage):
|
2018-07-19 02:47:32 +03:00
|
|
|
event = cls.Event(types.Message(
|
2018-04-05 21:14:22 +03:00
|
|
|
out=update.out,
|
|
|
|
mentioned=update.mentioned,
|
|
|
|
media_unread=update.media_unread,
|
|
|
|
silent=update.silent,
|
|
|
|
id=update.id,
|
2018-06-22 11:21:32 +03:00
|
|
|
# Note that to_id/from_id complement each other in private
|
|
|
|
# messages, depending on whether the message was outgoing.
|
2019-08-07 01:46:19 +03:00
|
|
|
to_id=types.PeerUser(update.user_id if update.out else self_id),
|
|
|
|
from_id=self_id if update.out else update.user_id,
|
2018-04-05 21:14:22 +03:00
|
|
|
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
|
|
|
|
))
|
|
|
|
elif isinstance(update, types.UpdateShortChatMessage):
|
2018-07-19 02:47:32 +03:00
|
|
|
event = cls.Event(types.Message(
|
2018-04-05 21:14:22 +03:00
|
|
|
out=update.out,
|
|
|
|
mentioned=update.mentioned,
|
|
|
|
media_unread=update.media_unread,
|
|
|
|
silent=update.silent,
|
|
|
|
id=update.id,
|
|
|
|
from_id=update.from_id,
|
|
|
|
to_id=types.PeerChat(update.chat_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
|
|
|
|
|
2018-06-16 18:35:24 +03:00
|
|
|
# Make messages sent to ourselves outgoing unless they're forwarded.
|
|
|
|
# This makes it consistent with official client's appearance.
|
|
|
|
ori = event.message
|
|
|
|
if isinstance(ori.to_id, types.PeerUser):
|
|
|
|
if ori.from_id == ori.to_id.user_id and not ori.fwd_from:
|
|
|
|
event.message.out = True
|
|
|
|
|
2018-07-11 12:22:43 +03:00
|
|
|
return event
|
2018-04-05 21:14:22 +03:00
|
|
|
|
2018-07-11 12:22:43 +03:00
|
|
|
def filter(self, event):
|
2018-06-20 12:49:18 +03:00
|
|
|
if self._no_check:
|
2018-04-05 21:14:22 +03:00
|
|
|
return event
|
|
|
|
|
|
|
|
if self.incoming and event.message.out:
|
|
|
|
return
|
|
|
|
if self.outgoing and not event.message.out:
|
|
|
|
return
|
2018-06-20 12:49:18 +03:00
|
|
|
if self.forwards is not None:
|
|
|
|
if bool(self.forwards) != bool(event.message.fwd_from):
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.from_users is not None:
|
|
|
|
if event.message.from_id not in self.from_users:
|
|
|
|
return
|
2018-04-05 21:14:22 +03:00
|
|
|
|
|
|
|
if self.pattern:
|
|
|
|
match = self.pattern(event.message.message or '')
|
|
|
|
if not match:
|
|
|
|
return
|
|
|
|
event.pattern_match = match
|
|
|
|
|
2018-07-11 12:22:43 +03:00
|
|
|
return super().filter(event)
|
2018-04-05 21:14:22 +03:00
|
|
|
|
|
|
|
class Event(EventCommon):
|
|
|
|
"""
|
2018-06-02 13:30:25 +03:00
|
|
|
Represents the event of a new message. This event can be treated
|
2019-06-01 17:27:53 +03:00
|
|
|
to all effects as a `Message <telethon.tl.custom.message.Message>`,
|
|
|
|
so please **refer to its documentation** to know what you can do
|
|
|
|
with this event.
|
2018-04-05 21:14:22 +03:00
|
|
|
|
|
|
|
Members:
|
2019-01-12 15:06:14 +03:00
|
|
|
message (`Message <telethon.tl.custom.message.Message>`):
|
2018-06-02 13:30:25 +03:00
|
|
|
This is the only difference with the received
|
2019-06-01 17:27:53 +03:00
|
|
|
`Message <telethon.tl.custom.message.Message>`, and will
|
2018-06-02 13:30:25 +03:00
|
|
|
return the `telethon.tl.custom.message.Message` itself,
|
|
|
|
not the text.
|
2018-04-05 21:14:22 +03:00
|
|
|
|
2019-06-01 17:27:53 +03:00
|
|
|
See `Message <telethon.tl.custom.message.Message>` for
|
|
|
|
the rest of available members and methods.
|
2018-06-20 21:03:44 +03:00
|
|
|
|
|
|
|
pattern_match (`obj`):
|
|
|
|
The resulting object from calling the passed ``pattern`` function.
|
|
|
|
Here's an example using a string (defaults to regex match):
|
|
|
|
|
|
|
|
>>> from telethon import TelegramClient, events
|
|
|
|
>>> client = TelegramClient(...)
|
|
|
|
>>>
|
2018-06-21 22:54:54 +03:00
|
|
|
>>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!'))
|
|
|
|
... async def handler(event):
|
2018-06-20 21:03:44 +03:00
|
|
|
... # In this case, the result is a ``Match`` object
|
2019-07-06 13:10:25 +03:00
|
|
|
... # since the `str` pattern was converted into
|
2018-06-20 21:03:44 +03:00
|
|
|
... # the ``re.compile(pattern).match`` function.
|
|
|
|
... print('Welcomed', event.pattern_match.group(1))
|
|
|
|
...
|
|
|
|
>>>
|
2018-04-05 21:14:22 +03:00
|
|
|
"""
|
|
|
|
def __init__(self, message):
|
2018-06-03 18:09:36 +03:00
|
|
|
self.__dict__['_init'] = False
|
2018-04-05 21:14:22 +03:00
|
|
|
if not message.out and isinstance(message.to_id, types.PeerUser):
|
|
|
|
# Incoming message (e.g. from a bot) has to_id=us, and
|
2018-09-22 20:18:42 +03:00
|
|
|
# from_id=bot (the actual "chat" from a user's perspective).
|
2018-04-05 21:14:22 +03:00
|
|
|
chat_peer = types.PeerUser(message.from_id)
|
|
|
|
else:
|
|
|
|
chat_peer = message.to_id
|
|
|
|
|
|
|
|
super().__init__(chat_peer=chat_peer,
|
|
|
|
msg_id=message.id, broadcast=bool(message.post))
|
|
|
|
|
2018-06-20 21:03:44 +03:00
|
|
|
self.pattern_match = None
|
2018-04-05 21:14:22 +03:00
|
|
|
self.message = message
|
|
|
|
|
2018-05-31 14:30:22 +03:00
|
|
|
def _set_client(self, client):
|
|
|
|
super()._set_client(client)
|
2019-03-28 12:47:15 +03:00
|
|
|
m = self.message
|
2019-05-01 18:07:12 +03:00
|
|
|
m._finish_init(client, self._entities, None)
|
|
|
|
self.__dict__['_init'] = True # No new attributes can be set
|
2019-03-28 12:47:15 +03:00
|
|
|
|
2018-06-03 18:09:36 +03:00
|
|
|
def __getattr__(self, item):
|
|
|
|
if item in self.__dict__:
|
|
|
|
return self.__dict__[item]
|
|
|
|
else:
|
|
|
|
return getattr(self.message, item)
|
|
|
|
|
|
|
|
def __setattr__(self, name, value):
|
|
|
|
if not self.__dict__['_init'] or name in self.__dict__:
|
|
|
|
self.__dict__[name] = value
|
|
|
|
else:
|
|
|
|
setattr(self.message, name, value)
|