From ef837b1a5325b080b4e282fa3a1aa3bc358ab565 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 7 Feb 2018 10:42:40 +0100 Subject: [PATCH] Add a NewMessage event to handle incoming messages --- telethon/events/__init__.py | 240 ++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/telethon/events/__init__.py b/telethon/events/__init__.py index d4d1e019..683945a7 100644 --- a/telethon/events/__init__.py +++ b/telethon/events/__init__.py @@ -12,3 +12,243 @@ class _EventBuilder(abc.ABC): @abc.abstractmethod def resolve(self, client): """Helper method to allow event builders to be resolved before usage""" + + +# 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, + require_input=True): + 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)): + 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.input_chat) 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: + """ + Represents the event of a new message. + + Members: + message (:obj:`Message`): + This is the original ``Message`` object. + + input_chat (:obj:`InputPeer`): + This is the input chat (private, group, megagroup or channel) + to which the message was sent. 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 not be available if the library can't + find the input chat. + + chat (:obj:`User` | :obj:`Chat` | :obj:`Channel`, optional): + 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. + + ``input_chat`` needs to be available (often the case). + + 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. + + input_sender (:obj:`InputPeer`): + This 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. + + 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). + + text (:obj:`str`): + The message text, markdown-formatted. + + raw_text (:obj:`str`): + The raw message text, ignoring any formatting. + + is_reply (:obj:`str`): + Whether the message is a reply to some other or not. + + reply_message (:obj:`Message`, optional): + This property 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. + + forward (:obj:`MessageFwdHeader`, optional): + The unmodified ``MessageFwdHeader``, if present. + + out (:obj:`bool`): + Whether the message is outgoing (i.e. you sent it from + another session) or incoming (i.e. someone else sent it). + """ + def __init__(self, message): + self._client = None + self.message = message + self._text = None + + self._chat = None + self._sender = None + + self.is_private = isinstance(message.to_id, types.PeerUser) + self.is_group = ( + isinstance(message.to_id, (types.PeerChat, types.PeerChannel)) + and not message.post + ) + self.is_channel = isinstance(message.to_id, types.PeerChannel) + + self.is_reply = bool(message.reply_to_msg_id) + self._reply_message = None + + def reply(self, message, as_reply=True): + """Replies to this message""" + self._client.send_message(self.message.to_id, message) + + @property + def input_chat(self): + # TODO If not found, getMessages to find the sender and chat + return self._client.get_input_entity(self.message.to_id) + + @property + def chat(self): + if self._chat is None: + # TODO Assert input entity is not None to avoid weird errors + self._chat = self._client.get_entity(self.input_chat) + return self._chat + + @property + def input_sender(self): + # TODO If not found, getMessages to find the sender and chat + return self._client.get_input_entity(self.message.from_id) + + @property + def sender(self): + if self._sender is None: + # TODO Assert input entity is not None to avoid weird errors + self._sender = self._client.get_entity(self.input_sender) + return self._sender + + @property + def text(self): + 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): + return self.message.message + + @property + def reply_message(self): + 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): + return self.message.fwd_from + + @property + def out(self): + return self.message.out