From 6893359f9dca0174765dd0299fbf78f2f9070585 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 15 Apr 2018 16:21:15 +0200 Subject: [PATCH 1/9] Fix tiny error in a docstring for #764 --- telethon/telegram_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 4d2fd54f..594b05cb 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -2486,7 +2486,11 @@ class TelegramClient(TelegramBareClient): a ValueError will be raised. Returns: - :tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`. + :tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel` + or :tl:`InputPeerSelf` if the parameter is ``'me'`` or ``'self'``. + + If you need to get the ID of yourself, you should use + `get_me` with ``input_peer=True``) instead. """ if peer in ('me', 'self'): return InputPeerSelf() From 03bebfb60003cd16efddef08e661451588c87eea Mon Sep 17 00:00:00 2001 From: Yifei Kong Date: Tue, 17 Apr 2018 19:01:23 +0800 Subject: [PATCH 2/9] Fix tiny docstring typo (#771) --- telethon/sessions/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/sessions/sqlite.py b/telethon/sessions/sqlite.py index e9a4a723..1f49d79c 100644 --- a/telethon/sessions/sqlite.py +++ b/telethon/sessions/sqlite.py @@ -18,7 +18,7 @@ CURRENT_VERSION = 3 # database version class SQLiteSession(MemorySession): """This session contains the required information to login into your - Telegram account. NEVER give the saved JSON file to anyone, since + Telegram account. NEVER give the saved session file to anyone, since they would gain instant access to all your messages and contacts. If you think the session has been compromised, close all the sessions From b0dda777fe4df83469201bcea5dc4185117d7937 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 18 Apr 2018 10:27:11 +0200 Subject: [PATCH 3/9] Support omitting the entity on client.edit_message --- telethon/telegram_client.py | 42 ++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 594b05cb..0aed8dc0 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -877,20 +877,26 @@ class TelegramClient(TelegramBareClient): result = [id_to_message[random_to_id[rnd]] for rnd in req.random_id] return result[0] if single else result - def edit_message(self, entity, message_id, message=None, parse_mode='md', + def edit_message(self, entity, message=None, text=None, parse_mode='md', link_preview=True): """ Edits the given message ID (to change its contents or disable preview). Args: - entity (`entity`): - From which chat to edit the message. + entity (`entity` | :tl:`Message`): + From which chat to edit the message. This can also be + the message to be edited, and the entity will be inferred + from it, so the next parameter will be assumed to be the + message text. - message_id (`str`): - The ID of the message (or ``Message`` itself) to be edited. + message (`int` | :tl:`Message` | `str`): + The ID of the message (or :tl:`Message` itself) to be edited. + If the `entity` was a :tl:`Message`, then this message will be + treated as the new text. - message (`str`, optional): - The new text of the message. + text (`str`, optional): + The new text of the message. Does nothing if the `entity` + was a :tl:`Message`. parse_mode (`str`, optional): Can be 'md' or 'markdown' for markdown-like parsing (default), @@ -901,6 +907,17 @@ class TelegramClient(TelegramBareClient): link_preview (`bool`, optional): Should the link preview be shown? + Examples: + + >>> client = TelegramClient(...).start() + >>> message = client.send_message('username', 'hello') + >>> + >>> client.edit_message('username', message, 'hello!') + >>> # or + >>> client.edit_message('username', message.id, 'Hello') + >>> # or + >>> client.edit_message(message, 'Hello!') + Raises: ``MessageAuthorRequiredError`` if you're not the author of the message but try editing it anyway. @@ -911,11 +928,16 @@ class TelegramClient(TelegramBareClient): Returns: The edited :tl:`Message`. """ - message, msg_entities = self._parse_message_text(message, parse_mode) + if isinstance(entity, Message): + text = message # Shift the parameters to the right + message = entity + entity = entity.to_id + + text, msg_entities = self._parse_message_text(text, parse_mode) request = EditMessageRequest( peer=self.get_input_entity(entity), - id=self._get_message_id(message_id), - message=message, + id=self._get_message_id(message), + message=text, no_webpage=not link_preview, entities=msg_entities ) From 1c2e9d2f2777223ba111d983c3e0af1a6d42716d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Apr 2018 09:36:34 +0200 Subject: [PATCH 4/9] Fix reply_to didn't override Message's reply on sending them --- telethon/telegram_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 0aed8dc0..0a80bd97 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -782,7 +782,9 @@ class TelegramClient(TelegramBareClient): and not isinstance(message.media, MessageMediaWebPage)): return self.send_file(entity, message.media) - if utils.get_peer_id(entity) == utils.get_peer_id(message.to_id): + if reply_to is not None: + reply_id = self._get_message_id(reply_to) + elif utils.get_peer_id(entity) == utils.get_peer_id(message.to_id): reply_id = message.reply_to_msg_id else: reply_id = None From 5dc43276bb755a14af9c01444b626659eec0ea65 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Apr 2018 09:44:33 +0200 Subject: [PATCH 5/9] Add missing caption when sending Message with media --- telethon/telegram_client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 0a80bd97..3abb8517 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -780,7 +780,9 @@ class TelegramClient(TelegramBareClient): if isinstance(message, Message): if (message.media and not isinstance(message.media, MessageMediaWebPage)): - return self.send_file(entity, message.media) + return self.send_file(entity, message.media, + caption=message.message, + entities=message.entities) if reply_to is not None: reply_id = self._get_message_id(reply_to) @@ -1495,7 +1497,14 @@ class TelegramClient(TelegramBareClient): entity = self.get_input_entity(entity) reply_to = self._get_message_id(reply_to) - caption, msg_entities = self._parse_message_text(caption, parse_mode) + + # Not document since it's subject to change. + # Needed when a Message is passed to send_message and it has media. + if 'entities' in kwargs: + msg_entities = kwargs['entities'] + else: + caption, msg_entities =\ + self._parse_message_text(caption, parse_mode) if not isinstance(file, (str, bytes, io.IOBase)): # The user may pass a Message containing media (or the media, From 544651caa798f17ca032d311047e529308afc24e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 22 Apr 2018 16:30:00 +0200 Subject: [PATCH 6/9] Retry on RpcCallFailError --- telethon/telegram_bare_client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index a8a43774..d1f4333f 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -11,7 +11,8 @@ from .crypto import rsa from .errors import ( RPCError, BrokenAuthKeyError, ServerError, FloodWaitError, FloodTestPhoneWaitError, TypeNotFoundError, UnauthorizedError, - PhoneMigrateError, NetworkMigrateError, UserMigrateError, AuthKeyError + PhoneMigrateError, NetworkMigrateError, UserMigrateError, AuthKeyError, + RpcCallFailError ) from .network import authenticator, MtProtoSender, Connection, ConnectionMode from .sessions import Session, SQLiteSession @@ -570,9 +571,9 @@ class TelegramBareClient: self._reconnect(new_dc=e.new_dc) return self._invoke(call_receive, *requests) - except ServerError as e: + except (ServerError, RpcCallFailError) as e: # Telegram is having some issues, just retry - __log__.error('Telegram servers are having internal errors %s', e) + __log__.warning('Telegram is having internal issues: %s', e) except (FloodWaitError, FloodTestPhoneWaitError) as e: __log__.warning('Request invoked too often, wait %ds', e.seconds) From 6ecef42ec7d44747be116c22f00a987f802d04c1 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 22 Apr 2018 16:30:14 +0200 Subject: [PATCH 7/9] Add search/filter/from_user parameters to iter_messages --- telethon/telegram_client.py | 72 ++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 3abb8517..c6c574c0 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -58,7 +58,7 @@ from .tl.functions.messages import ( SendMessageRequest, GetChatsRequest, GetAllDraftsRequest, CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest, UploadMediaRequest, EditMessageRequest, GetFullChatRequest, - ForwardMessagesRequest + ForwardMessagesRequest, SearchRequest ) from .tl.functions import channels @@ -990,10 +990,14 @@ class TelegramClient(TelegramBareClient): def iter_messages(self, entity, limit=20, offset_date=None, offset_id=0, max_id=0, min_id=0, add_offset=0, + search=None, filter=None, from_user=None, batch_size=100, wait_time=None, _total=None): """ Iterator over the message history for the specified entity. + If either `search`, `filter` or `from_user` are provided, + :tl:`messages.Search` will be used instead of :tl:`messages.getHistory`. + Args: entity (`entity`): The entity from whom to retrieve the message history. @@ -1025,6 +1029,17 @@ class TelegramClient(TelegramBareClient): Additional message offset (all of the specified offsets + this offset = older messages). + search (`str`): + The string to be used as a search query. + + filter (:tl:`MessagesFilter` | `type`): + The filter to use when returning messages. For instance, + :tl:`InputMessagesFilterPhotos` would yield only messages + containing photos. + + from_user (`entity`): + Only messages from this user will be returned. + batch_size (`int`): Messages will be returned in chunks of this size (100 is the maximum). While it makes no sense to modify this value, @@ -1056,15 +1071,37 @@ class TelegramClient(TelegramBareClient): """ entity = self.get_input_entity(entity) limit = float('inf') if limit is None else int(limit) + if search is not None or filter or from_user: + request = SearchRequest( + peer=entity, + q=search or '', + filter=filter() if isinstance(filter, type) else filter, + min_date=None, + max_date=offset_date, + offset_id=offset_id, + add_offset=add_offset, + limit=1, + max_id=max_id, + min_id=min_id, + from_id=self.get_input_entity(from_user) if from_user else None + ) + else: + request = GetHistoryRequest( + peer=entity, + limit=1, + offset_date=offset_date, + offset_id=offset_id, + min_id=min_id, + max_id=max_id, + add_offset=add_offset, + hash=0 + ) + if limit == 0: if not _total: return # No messages, but we still need to know the total message count - result = self(GetHistoryRequest( - peer=entity, limit=1, - offset_date=None, offset_id=0, max_id=0, min_id=0, - add_offset=0, hash=0 - )) + result = self(request) _total[0] = getattr(result, 'count', len(result.messages)) return @@ -1075,17 +1112,8 @@ class TelegramClient(TelegramBareClient): batch_size = min(max(batch_size, 1), 100) while have < limit: # Telegram has a hard limit of 100 - real_limit = min(limit - have, batch_size) - r = self(GetHistoryRequest( - peer=entity, - limit=real_limit, - offset_date=offset_date, - offset_id=offset_id, - max_id=max_id, - min_id=min_id, - add_offset=add_offset, - hash=0 - )) + request.limit = min(limit - have, batch_size) + r = self(request) if _total: _total[0] = getattr(r, 'count', len(r.messages)) @@ -1120,11 +1148,15 @@ class TelegramClient(TelegramBareClient): yield message have += 1 - if len(r.messages) < real_limit: + if len(r.messages) < request.limit: break - offset_id = r.messages[-1].id - offset_date = r.messages[-1].date + request.offset_id = r.messages[-1].id + if isinstance(request, GetHistoryRequest): + request.offset_date = r.messages[-1].date + else: + request.max_date = r.messages[-1].date + time.sleep(wait_time) def get_messages(self, *args, **kwargs): From f31ca142a3ee404cb24e50fc180f2383321f0082 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 23 Apr 2018 11:05:38 +0200 Subject: [PATCH 8/9] Support autocast to InputMessage --- telethon/events/chataction.py | 4 +--- telethon/events/common.py | 8 ++------ telethon/events/messageread.py | 6 ++---- telethon/events/newmessage.py | 6 ++---- telethon/utils.py | 17 ++++++++++++++++- telethon_generator/generators/tlobject.py | 3 ++- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index cf6faae5..3ca18f5e 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -204,9 +204,7 @@ class ChatAction(EventBuilder): if isinstance(self._pinned_message, int) and self.input_chat: r = self._client(functions.channels.GetMessagesRequest( - self._input_chat, [ - types.InputMessageID(self._pinned_message) - ] + self._input_chat, [self._pinned_message] )) try: self._pinned_message = next( diff --git a/telethon/events/common.py b/telethon/events/common.py index 057be9f4..54289726 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -118,15 +118,11 @@ class EventCommon(abc.ABC): try: if isinstance(chat, types.InputPeerChannel): result = self._client( - functions.channels.GetMessagesRequest(chat, [ - types.InputMessageID(msg_id) - ]) + functions.channels.GetMessagesRequest(chat, [msg_id]) ) else: result = self._client( - functions.messages.GetMessagesRequest([ - types.InputMessageID(msg_id) - ]) + functions.messages.GetMessagesRequest([msg_id]) ) except RPCError: return None, None diff --git a/telethon/events/messageread.py b/telethon/events/messageread.py index da7c39ea..16db40bb 100644 --- a/telethon/events/messageread.py +++ b/telethon/events/messageread.py @@ -101,16 +101,14 @@ class MessageRead(EventBuilder): if not chat: self._messages = [] elif isinstance(chat, types.InputPeerChannel): - ids = [types.InputMessageID(x) for x in self._message_ids] self._messages =\ self._client(functions.channels.GetMessagesRequest( - chat, ids + chat, self._message_ids )).messages else: - ids = [types.InputMessageID(x) for x in self._message_ids] self._messages =\ self._client(functions.messages.GetMessagesRequest( - ids + self._message_ids )).messages return self._messages diff --git a/telethon/events/newmessage.py b/telethon/events/newmessage.py index f6761485..ec8572cf 100644 --- a/telethon/events/newmessage.py +++ b/telethon/events/newmessage.py @@ -297,13 +297,11 @@ class NewMessage(EventBuilder): if self._reply_message is None: if isinstance(self.input_chat, types.InputPeerChannel): r = self._client(functions.channels.GetMessagesRequest( - self.input_chat, [ - types.InputMessageID(self.message.reply_to_msg_id) - ] + self.input_chat, [self.message.reply_to_msg_id] )) else: r = self._client(functions.messages.GetMessagesRequest( - [types.InputMessageID(self.message.reply_to_msg_id)] + [self.message.reply_to_msg_id] )) if not isinstance(r, types.messages.MessagesNotModified): self._reply_message = r.messages[0] diff --git a/telethon/utils.py b/telethon/utils.py index ee489f90..5bccfc10 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -25,7 +25,7 @@ from .tl.types import ( InputPhotoEmpty, FileLocation, ChatPhotoEmpty, UserProfilePhotoEmpty, FileLocationUnavailable, InputMediaUploadedDocument, ChannelFull, InputMediaUploadedPhoto, DocumentAttributeFilename, photos, - TopPeer, InputNotifyPeer + TopPeer, InputNotifyPeer, InputMessageID ) from .tl.types.contacts import ResolvedPeer @@ -333,6 +333,21 @@ def get_input_media(media, is_photo=False): _raise_cast_fail(media, 'InputMedia') +def get_input_message(message): + """Similar to :meth:`get_input_peer`, but for input messages.""" + try: + if isinstance(message, int): # This case is really common too + return InputMessageID(message) + elif message.SUBCLASS_OF_ID == 0x54b6bcc5: # crc32(b'InputMessage'): + return message + elif message.SUBCLASS_OF_ID == 0x790009e3: # crc32(b'Message'): + return InputMessageID(message.id) + except AttributeError: + pass + + _raise_cast_fail(message, 'InputMedia') + + def is_image(file): """ Returns ``True`` if the file extension looks like an image file to Telegram. diff --git a/telethon_generator/generators/tlobject.py b/telethon_generator/generators/tlobject.py index a2e02efb..74debc10 100644 --- a/telethon_generator/generators/tlobject.py +++ b/telethon_generator/generators/tlobject.py @@ -18,7 +18,8 @@ AUTO_CASTS = { 'InputChannel': 'utils.get_input_channel(client.get_input_entity({}))', 'InputUser': 'utils.get_input_user(client.get_input_entity({}))', 'InputMedia': 'utils.get_input_media({})', - 'InputPhoto': 'utils.get_input_photo({})' + 'InputPhoto': 'utils.get_input_photo({})', + 'InputMessage': 'utils.get_input_message({})' } BASE_TYPES = ('string', 'bytes', 'int', 'long', 'int128', From ab91bc2829647623eed99d524677d4a6368ba807 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 23 Apr 2018 15:33:44 +0200 Subject: [PATCH 9/9] Add missing InputPhoto/Document -> InputMedia autocast --- telethon/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telethon/utils.py b/telethon/utils.py index 5bccfc10..0c6693b1 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -255,8 +255,12 @@ def get_input_media(media, is_photo=False): it will be treated as an :tl:`InputMediaUploadedPhoto`. """ try: - if media.SUBCLASS_OF_ID == 0xfaf846f4: # crc32(b'InputMedia'): + if media.SUBCLASS_OF_ID == 0xfaf846f4: # crc32(b'InputMedia') return media + elif media.SUBCLASS_OF_ID == 0x846363e0: # crc32(b'InputPhoto') + return InputMediaPhoto(media) + elif media.SUBCLASS_OF_ID == 0xf33fdb68: # crc32(b'InputDocument') + return InputMediaDocument(media) except AttributeError: _raise_cast_fail(media, 'InputMedia')