From ee1e4e18f6427d7de796b9ca7b07bb3058266495 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 6 May 2018 11:41:42 +0200 Subject: [PATCH 1/4] Clean-up download_profile_photo and add missing cases --- telethon/telegram_client.py | 57 +++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 23af0f57..28af7f1c 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -1874,59 +1874,55 @@ class TelegramClient(TelegramBareClient): ``None`` if no photo was provided, or if it was Empty. On success the file path is returned since it may differ from the one given. """ - photo = entity - possible_names = [] - try: - is_entity = entity.SUBCLASS_OF_ID in ( - 0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697 - ) - except AttributeError: - return None # Not even a TLObject as attribute access failed - - if is_entity: - # Maybe it is an user or a chat? Or their full versions? - # - # The hexadecimal numbers above are simply: - # hex(crc32(x.encode('ascii'))) for x in - # ('User', 'Chat', 'UserFull', 'ChatFull') + # hex(crc32(x.encode('ascii'))) for x in + # ('User', 'Chat', 'UserFull', 'ChatFull') + ENTITIES = (0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697) + # ('InputPeer', 'InputUser', 'InputChannel') + INPUTS = (0xc91c90b6, 0xe669bf46, 0x40f202fd) + if not isinstance(entity, TLObject) or entity.SUBCLASS_OF_ID in INPUTS: entity = self.get_entity(entity) + + possible_names = [] + if entity.SUBCLASS_OF_ID not in ENTITIES: + photo = entity + else: if not hasattr(entity, 'photo'): # Special case: may be a ChatFull with photo:Photo # This is different from a normal UserProfilePhoto and Chat - if hasattr(entity, 'chat_photo'): - return self._download_photo( - entity.chat_photo, file, - date=None, progress_callback=None - ) - else: - # Give up + if not hasattr(entity, 'chat_photo'): return None + return self._download_photo(entity.chat_photo, file, + date=None, progress_callback=None) + for attr in ('username', 'first_name', 'title'): possible_names.append(getattr(entity, attr, None)) photo = entity.photo - if not isinstance(photo, UserProfilePhoto) and \ - not isinstance(photo, ChatPhoto): - return None + if isinstance(photo, (UserProfilePhoto, ChatPhoto)): + loc = photo.photo_big if download_big else photo.photo_small + else: + try: + loc = utils.get_input_location(photo) + except TypeError: + return None - photo_location = photo.photo_big if download_big else photo.photo_small file = self._get_proper_filename( file, 'profile_photo', '.jpg', possible_names=possible_names ) - # Download the media with the largest size input file location try: self.download_file( InputFileLocation( - volume_id=photo_location.volume_id, - local_id=photo_location.local_id, - secret=photo_location.secret + volume_id=loc.volume_id, + local_id=loc.local_id, + secret=loc.secret ), file ) + return file except LocationInvalidError: # See issue #500, Android app fails as of v4.6.0 (1155). # The fix seems to be using the full channel chat photo. @@ -1940,7 +1936,6 @@ class TelegramClient(TelegramBareClient): else: # Until there's a report for chats, no need to. return None - return file def download_media(self, message, file=None, progress_callback=None): """ From 2045e0056368c765570f4fc97d6e8bd7b0ca92c3 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 6 May 2018 11:46:04 +0200 Subject: [PATCH 2/4] Stop manually constructing InputFileLocation --- telethon/telegram_client.py | 38 ++++++++----------------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 28af7f1c..f537fbcd 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -72,7 +72,6 @@ from .tl.functions.channels import ( ) from .tl.types import ( DocumentAttributeAudio, DocumentAttributeFilename, - InputDocumentFileLocation, InputFileLocation, InputMediaUploadedDocument, InputMediaUploadedPhoto, InputPeerEmpty, Message, MessageMediaContact, MessageMediaDocument, MessageMediaPhoto, InputUserSelf, UserProfilePhoto, ChatPhoto, UpdateMessageID, @@ -1914,14 +1913,7 @@ class TelegramClient(TelegramBareClient): ) try: - self.download_file( - InputFileLocation( - volume_id=loc.volume_id, - local_id=loc.local_id, - secret=loc.secret - ), - file - ) + self.download_file(loc, file) return file except LocationInvalidError: # See issue #500, Android app fails as of v4.6.0 (1155). @@ -2016,16 +2008,8 @@ class TelegramClient(TelegramBareClient): f.close() return file - self.download_file( - InputFileLocation( - volume_id=photo.location.volume_id, - local_id=photo.location.local_id, - secret=photo.location.secret - ), - file, - file_size=photo.size, - progress_callback=progress_callback - ) + self.download_file(photo.location, file, file_size=photo.size, + progress_callback=progress_callback) return file def _download_document(self, document, file, date, progress_callback): @@ -2061,16 +2045,8 @@ class TelegramClient(TelegramBareClient): date=date, possible_names=possible_names ) - self.download_file( - InputDocumentFileLocation( - id=document.id, - access_hash=document.access_hash, - version=document.version - ), - file, - file_size=file_size, - progress_callback=progress_callback - ) + self.download_file(document, file, file_size=file_size, + progress_callback=progress_callback) return file @staticmethod @@ -2178,8 +2154,10 @@ class TelegramClient(TelegramBareClient): Downloads the given input location to a file. Args: - input_location (:tl:`InputFileLocation`): + input_location (:tl:`FileLocation` | :tl:`InputFileLocation`): The file location from which the file will be downloaded. + See `telethon.utils.get_input_location` source for a complete + list of supported types. file (`str` | `file`, optional): The output file path, directory, or stream-like object. From f442e01560ba7ddb6a0010ee6d474f788a512b4b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 6 May 2018 13:03:30 +0200 Subject: [PATCH 3/4] Documentation enhancements --- readthedocs/extra/advanced-usage/sessions.rst | 8 ++++++++ readthedocs/extra/basic/creating-a-client.rst | 7 +++++++ telethon/telegram_client.py | 16 +++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/readthedocs/extra/advanced-usage/sessions.rst b/readthedocs/extra/advanced-usage/sessions.rst index 592d8334..a2031f16 100644 --- a/readthedocs/extra/advanced-usage/sessions.rst +++ b/readthedocs/extra/advanced-usage/sessions.rst @@ -9,6 +9,14 @@ the ``session``, and defaults to be the session name (or full path). That is, if you create a ``TelegramClient('anon')`` instance and connect, an ``anon.session`` file will be created on the working directory. +Note that if you pass a string it will be a file in the current working +directory, although you can also pass absolute paths. + +The session file contains enough information for you to login without +re-sending the code, so if you have to enter the code more than once, +maybe you're changing the working directory, renaming or removing the +file, or using random names. + These database files using ``sqlite3`` contain the required information to talk to the Telegram servers, such as to which IP the client should connect, port, authorization key so that messages can be encrypted, and so on. diff --git a/readthedocs/extra/basic/creating-a-client.rst b/readthedocs/extra/basic/creating-a-client.rst index 7a110e0d..515d68ef 100644 --- a/readthedocs/extra/basic/creating-a-client.rst +++ b/readthedocs/extra/basic/creating-a-client.rst @@ -39,6 +39,13 @@ Note that ``'some_name'`` will be used to save your session (persistent information such as access key and others) as ``'some_name.session'`` in your disk. This is by default a database file using Python's ``sqlite3``. +.. note:: + + It's important that the library always accesses the same session file so + that you don't need to re-send the code over and over again. By default it + creates the file in your working directory, but absolute paths work too. + + Before using the client, you must be connected to Telegram. Doing so is very easy: diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index f537fbcd..4a131f9a 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -104,6 +104,14 @@ class TelegramClient(TelegramBareClient): used otherwise. If it's ``None``, the session will not be saved, and you should call :meth:`.log_out()` when you're done. + Note that if you pass a string it will be a file in the current + working directory, although you can also pass absolute paths. + + The session file contains enough information for you to login + without re-sending the code, so if you have to enter the code + more than once, maybe you're changing the working directory, + renaming or removing the file, or using random names. + api_id (`int` | `str`): The API ID you obtained from https://my.telegram.org. @@ -532,7 +540,8 @@ class TelegramClient(TelegramBareClient): offset_peer=InputPeerEmpty(), _total=None): """ Returns an iterator over the dialogs, yielding 'limit' at most. - Dialogs are the open "chats" or conversations with other people. + Dialogs are the open "chats" or conversations with other people, + groups you have joined, or channels you are subscribed to. Args: limit (`int` | `None`): @@ -738,6 +747,11 @@ class TelegramClient(TelegramBareClient): message (`str` | :tl:`Message`): The message to be sent, or another message object to resend. + The maximum length for a message is 35,000 bytes or 4,096 + characters. Longer messages will not be sliced automatically, + and you should slice them manually if the text to send is + longer than said length. + reply_to (`int` | :tl:`Message`, optional): Whether to reply to a message or not. If an integer is provided, it should be the ID of the message that it should reply to. From ee0f95b1562099ee98ae186e5b02e7eeaff48a28 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 6 May 2018 13:06:44 +0200 Subject: [PATCH 4/4] Fix library expects bytes instead strings on mtproto.tl --- telethon_generator/data/mtproto_api.tl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/telethon_generator/data/mtproto_api.tl b/telethon_generator/data/mtproto_api.tl index b3aa44e1..aa5e4c97 100644 --- a/telethon_generator/data/mtproto_api.tl +++ b/telethon_generator/data/mtproto_api.tl @@ -10,17 +10,17 @@ dummyHttpWait = HttpWait; //int128 4*[ int ] = Int128; //int256 8*[ int ] = Int256; -resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector = ResPQ; +resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector = ResPQ; -p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data; -p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data; +p_q_inner_data#83c95aec pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data; +p_q_inner_data_temp#3c6a84d4 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data; server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params; -server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params; +server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:bytes = Server_DH_Params; -server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data; +server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:bytes g_a:bytes server_time:int = Server_DH_inner_data; -client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data; +client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:bytes = Client_DH_Inner_Data; dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer; dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer; @@ -28,7 +28,7 @@ dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = S bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; -//rpc_result#f35c6d01 req_msg_id:long result:string = RpcResult; +//rpc_result#f35c6d01 req_msg_id:long result:bytes = RpcResult; rpc_error#2144ca19 error_code:int error_message:string = RpcError; rpc_answer_unknown#5e2ad36e = RpcDropAnswer; @@ -46,10 +46,10 @@ destroy_session_none#62d350c9 session_id:long = DestroySessionRes; new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession; //msg_container#73f1f8dc messages:vector<%Message> = MessageContainer; -//message msg_id:long seqno:int bytes:int body:string = Message; +//message msg_id:long seqno:int bytes:int body:bytes = Message; //msg_copy#e06046b2 orig_message:Message = MessageCopy; -gzip_packed#3072cfa1 packed_data:string = Object; +gzip_packed#3072cfa1 packed_data:bytes = Object; msgs_ack#62d6b459 msg_ids:Vector = MsgsAck; @@ -63,15 +63,15 @@ msgs_all_info#8cc0d131 msg_ids:Vector info:string = MsgsAllInfo; msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo; msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo; -rsa_public_key n:string e:string = RSAPublicKey; +rsa_public_key n:string e:bytes = RSAPublicKey; ---functions--- req_pq_multi#be7e8ef1 nonce:int128 = ResPQ; -req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params; +req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:string = Server_DH_Params; -set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer; +set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer; rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer; get_future_salts#b921bd04 num:int = FutureSalts;