Telethon/telethon/events/newmessage.py

244 lines
9.9 KiB
Python
Raw Normal View History

2018-08-21 13:14:32 +03:00
import asyncio
2018-04-05 21:14:22 +03:00
import re
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):
"""
Represents a new message event builder.
Args:
incoming (`bool`, optional):
If set to ``True``, only **incoming** messages will be handled.
Mutually exclusive with ``outgoing`` (can only set one of either).
outgoing (`bool`, optional):
If set to ``True``, only **outgoing** messages will be handled.
Mutually exclusive with ``incoming`` (can only set one of either).
from_users (`entity`, optional):
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).
forwards (`bool`, optional):
Whether forwarded messages should be handled or not. By default,
both forwarded and normal messages are included. If it's ``True``
*only* forwards will be handled. If it's ``False`` only messages
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
against the message, a callable function that returns ``True``
if a message is acceptable, or a compiled regex pattern.
"""
def __init__(self, chats=None, *, blacklist_chats=False, func=None,
incoming=None, outgoing=None,
from_users=None, forwards=None, pattern=None):
if incoming and outgoing:
incoming = outgoing = None # Same as no filter
elif incoming is not None and outgoing is None:
outgoing = not incoming
elif outgoing is not None and incoming is None:
2018-06-02 13:38:03 +03:00
incoming = not outgoing
elif all(x is not None and not x for x in (incoming, outgoing)):
raise ValueError("Don't create an event handler if you "
"don't want neither incoming nor outgoing!")
2018-04-05 21:14:22 +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
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')
# Should we short-circuit? E.g. perform no check at all
self._no_check = all(x is None for x in (
self.chats, self.incoming, self.outgoing, self.pattern,
self.from_users, self.forwards, self.from_users, self.func
))
async def _resolve(self, client):
await super()._resolve(client)
self.from_users = await _into_id_set(client, self.from_users)
2018-07-19 02:47:32 +03:00
@classmethod
def build(cls, update):
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,
# Note that to_id/from_id complement each other in private
# messages, depending on whether the message was outgoing.
to_id=types.PeerUser(
2018-07-19 02:47:32 +03:00
update.user_id if update.out else cls.self_id
),
2018-07-19 02:47:32 +03:00
from_id=cls.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
# 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
event._entities = update._entities
return event
2018-04-05 21:14:22 +03:00
2018-07-11 12:22:43 +03:00
def filter(self, event):
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
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):
"""
Represents the event of a new message. This event can be treated
to all effects as a `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:
message (`Message <telethon.tl.custom.message.Message>`):
This is the only difference with the received
`telethon.tl.custom.message.Message`, and will
return the `telethon.tl.custom.message.Message` itself,
not the text.
2018-04-05 21:14:22 +03:00
See `telethon.tl.custom.message.Message` for the rest of
available members and methods.
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(...)
>>>
>>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!'))
... async def handler(event):
... # In this case, the result is a ``Match`` object
... # since the ``str`` pattern was converted into
... # 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):
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))
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)
self.message._finish_init(client, self._entities, None)
self.__dict__['_init'] = True # No new attributes can be set
def _load_entities(self):
m = self.message
m._chat, m._input_chat = self._get_entity_pair(m.chat_id)
m._sender, m._input_sender = self._get_entity_pair(m.sender_id)
m._via_bot, m._via_input_bot = self._get_entity_pair(m.via_bot_id)
if not m.forward:
forward_ok = True
else:
f = m.forward
f._chat, f._input_chat = self._get_entity_pair(f.chat_id)
f._sender, f._input_sender = self._get_entity_pair(f.sender_id)
forward_ok = (
(not f.chat_id or f._input_chat is not None)
and (not f.sender_id or f._input_sender is not None)
)
return (
m._input_chat is not None
and (not m.sender_id or m._input_sender is not None)
and (not m.via_bot_id or m._via_input_bot is not None)
and forward_ok
)
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)