From 92088383f73e2c473da33aaf8172c1b6d8775818 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 15 Jun 2017 09:41:01 +0200 Subject: [PATCH 01/19] Fix get_message_history not returning sender on channels (closes #110) --- telethon/telegram_client.py | 6 ++++-- telethon_examples/interactive_telegram_client.py | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 50727ce2..63252de2 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -447,8 +447,10 @@ class TelegramClient(TelegramBareClient): total_messages = getattr(result, 'count', len(result.messages)) # Iterate over all the messages and find the sender User - entities = [find_user_or_chat(msg.from_id, result.users, result.chats) - for msg in result.messages] + entities = [find_user_or_chat(m.from_id, result.users, result.chats) + if m.from_id is not None else + find_user_or_chat(m.to_id, result.users, result.chats) + for m in result.messages] return total_messages, result.messages, entities diff --git a/telethon_examples/interactive_telegram_client.py b/telethon_examples/interactive_telegram_client.py index 6784d411..748f4142 100644 --- a/telethon_examples/interactive_telegram_client.py +++ b/telethon_examples/interactive_telegram_client.py @@ -162,7 +162,14 @@ class InteractiveTelegramClient(TelegramClient): for msg, sender in zip( reversed(messages), reversed(senders)): # Get the name of the sender if any - name = sender.first_name if sender else '???' + if sender: + name = getattr(sender, 'first_name', None) + if not name: + name = getattr(sender, 'title') + if not name: + name = '???' + else: + name = '???' # Format the message content if getattr(msg, 'media', None): From 3fccfd40e63f457659d3b23ba1a65a13d1c05707 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 15 Jun 2017 15:25:05 +0200 Subject: [PATCH 02/19] Show error messages on non-specialized error classes (closes #113) --- telethon/errors/rpc_errors.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/telethon/errors/rpc_errors.py b/telethon/errors/rpc_errors.py index 55d03ff3..5c938641 100644 --- a/telethon/errors/rpc_errors.py +++ b/telethon/errors/rpc_errors.py @@ -38,6 +38,10 @@ class ForbiddenError(RPCError): code = 403 message = 'FORBIDDEN' + def __init__(self, message): + super().__init__(self, message) + self.message = message + class NotFoundError(RPCError): """ @@ -46,6 +50,10 @@ class NotFoundError(RPCError): code = 404 message = 'NOT_FOUND' + def __init__(self, message): + super().__init__(self, message) + self.message = message + class FloodError(RPCError): """ @@ -67,6 +75,10 @@ class ServerError(RPCError): code = 500 message = 'INTERNAL' + def __init__(self, message): + super().__init__(self, message) + self.message = message + class BadMessageError(Exception): """Occurs when handling a bad_message_notification""" From c02fbae5aa311047a1973c4509b02069120c35ba Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 15 Jun 2017 15:50:44 +0200 Subject: [PATCH 03/19] Allow creating a new parallel connection (closes #102) --- .gitignore | 3 ++ telethon/telegram_client.py | 57 ++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f737e858..aef0b91f 100755 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ telethon/tl/all_tlobjects.py usermedia/ api/settings +# Quick tests should live in this file +example.py + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 63252de2..d49b5855 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -145,21 +145,13 @@ class TelegramClient(TelegramBareClient): self._cached_clients.clear() - def reconnect(self, new_dc=None): - """Disconnects and connects again (effectively reconnecting). - - If 'new_dc' is not None, the current authorization key is - removed, the DC used is switched, and a new connection is made. - - *args will be ignored. - """ - super(TelegramClient, self).reconnect(new_dc=new_dc) - # endregion - # region Working with different Data Centers + # region Working with different connections - def _get_exported_client(self, dc_id, init_connection=False): + def _get_exported_client(self, dc_id, + init_connection=False, + bypass_cache=False): """Gets a cached exported TelegramBareClient for the desired DC. If it's the first time retrieving the TelegramBareClient, the @@ -168,12 +160,16 @@ class TelegramClient(TelegramBareClient): If after using the sender a ConnectionResetError is raised, this method should be called again with init_connection=True - in order to perform the reconnection.""" + in order to perform the reconnection. + + If bypass_cache is True, a new client will be exported and + it will not be cached. + """ # Thanks badoualy/kotlogram on /telegram/api/DefaultTelegramClient.kt # for clearly showing how to export the authorization! ^^ client = self._cached_clients.get(dc_id) - if client: + if client and not bypass_cache: if init_connection: client.reconnect() return client @@ -191,10 +187,37 @@ class TelegramClient(TelegramBareClient): client = TelegramBareClient(session, self.api_id, self.api_hash) client.connect(exported_auth=export_auth) - # Don't go through this expensive process every time. - self._cached_clients[dc_id] = client + if not bypass_cache: + # Don't go through this expensive process every time. + self._cached_clients[dc_id] = client return client + def create_new_connection(self, on_dc=None): + """Creates a new connection which can be used in parallel + with the original TelegramClient. A TelegramBareClient + will be returned already connected, and the caller is + responsible to disconnect it. + + If 'on_dc' is None, the new client will run on the same + data center as the current client (most common case). + + If the client is meant to be used on a different data + center, the data center ID should be specified instead. + + Note that TelegramBareClients will not handle automatic + reconnection (i.e. switching to another data center to + download media), and InvalidDCError will be raised in + such case. + """ + if on_dc is None: + client = TelegramBareClient(self.session, self.api_id, self.api_hash, + proxy=self.proxy) + client.connect() + else: + client = self._get_exported_client(on_dc, bypass_cache=True) + + return client + # endregion # region Telegram requests functions @@ -476,6 +499,8 @@ class TelegramClient(TelegramBareClient): # endregion + # region Uploading files + def send_photo_file(self, input_file, entity, caption=''): """Sends a previously uploaded input_file (which should be a photo) to the given entity (or input peer)""" From b8e46446ba7302ff11bc4c632bf072f4cd5f16a2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 15 Jun 2017 16:35:40 +0200 Subject: [PATCH 04/19] Automatically call .get_input_peer on the requests that need it --- telethon_generator/tl_generator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index 5ccaa27c..cffb0f75 100755 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -186,6 +186,12 @@ class TLGenerator: builder.writeln('from {}.tl.mtproto_request import MTProtoRequest' .format('.' * depth)) + if any(a for a in tlobject.args if a.type == 'InputPeer'): + # We can automatically convert a normal peer to an InputPeer, + # it will make invoking a lot of requests a lot simpler. + builder.writeln('from {}.utils import get_input_peer' + .format('.' * depth)) + if any(a for a in tlobject.args if a.can_be_inferred): # Currently only 'random_id' needs 'os' to be imported builder.writeln('import os') @@ -306,6 +312,10 @@ class TLGenerator: ) else: raise ValueError('Cannot infer a value for ', arg) + elif arg.type == 'InputPeer': + # Well-known case, auto-cast it to the right type + builder.writeln( + 'self.{0} = get_input_peer({0})'.format(arg.name)) else: builder.writeln('self.{0} = {0}'.format(arg.name)) From 86d45cc27675df5d43928961bb1fb1e44e6db486 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 15 Jun 2017 17:03:59 +0200 Subject: [PATCH 05/19] Improve .get_input_peer and use it only when creating requests* This avoids cyclic dependencies, so types requiring an InputPeer as a parameter will NOT convert faulty types to the right ones. --- telethon/utils.py | 28 ++++++++++++++++++++-------- telethon_generator/tl_generator.py | 5 +++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/telethon/utils.py b/telethon/utils.py index 7d11ef17..9f22794b 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -5,9 +5,10 @@ to convert between an entity like an User, Chat, etc. into its Input version) from mimetypes import add_type, guess_extension from .tl.types import ( - Channel, Chat, ChatPhoto, InputPeerChannel, InputPeerChat, InputPeerUser, - MessageMediaDocument, MessageMediaPhoto, PeerChannel, PeerChat, PeerUser, - User, UserProfilePhoto) + Channel, ChannelForbidden, Chat, ChatEmpty, ChatForbidden, ChatFull, + ChatPhoto, InputPeerChannel, InputPeerChat, InputPeerUser, InputPeerEmpty, + InputPeerSelf, MessageMediaDocument, MessageMediaPhoto, PeerChannel, + PeerChat, PeerUser, User, UserFull, UserProfilePhoto) def get_display_name(entity): @@ -46,18 +47,29 @@ def get_extension(media): def get_input_peer(entity): """Gets the input peer for the given "entity" (user, chat or channel). A ValueError is raised if the given entity isn't a supported type.""" - if (isinstance(entity, InputPeerUser) or - isinstance(entity, InputPeerChat) or - isinstance(entity, InputPeerChannel)): + if any(isinstance(entity, c) for c in ( + InputPeerUser, InputPeerChat, InputPeerChannel, + InputPeerSelf, InputPeerEmpty)): return entity if isinstance(entity, User): return InputPeerUser(entity.id, entity.access_hash) - if isinstance(entity, Chat): + + if any(isinstance(entity, c) for c in ( + Chat, ChatEmpty, ChatForbidden)): return InputPeerChat(entity.id) - if isinstance(entity, Channel): + + if any(isinstance(entity, c) for c in ( + Channel, ChannelForbidden)): return InputPeerChannel(entity.id, entity.access_hash) + # Less common cases + if isinstance(entity, UserFull): + return InputPeerUser(entity.user.id, entity.user.access_hash) + + if isinstance(entity, ChatFull): + return InputPeerChat(entity.id) + raise ValueError('Cannot cast {} to any kind of InputPeer.' .format(type(entity).__name__)) diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index cffb0f75..042c29dc 100755 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -186,7 +186,8 @@ class TLGenerator: builder.writeln('from {}.tl.mtproto_request import MTProtoRequest' .format('.' * depth)) - if any(a for a in tlobject.args if a.type == 'InputPeer'): + if tlobject.is_function and \ + any(a for a in tlobject.args if a.type == 'InputPeer'): # We can automatically convert a normal peer to an InputPeer, # it will make invoking a lot of requests a lot simpler. builder.writeln('from {}.utils import get_input_peer' @@ -312,7 +313,7 @@ class TLGenerator: ) else: raise ValueError('Cannot infer a value for ', arg) - elif arg.type == 'InputPeer': + elif arg.type == 'InputPeer' and tlobject.is_function: # Well-known case, auto-cast it to the right type builder.writeln( 'self.{0} = get_input_peer({0})'.format(arg.name)) From 13199f737e84d0b072b04735c2e0023e2292d116 Mon Sep 17 00:00:00 2001 From: feodoran Date: Fri, 16 Jun 2017 09:11:49 +0200 Subject: [PATCH 06/19] Handle more cases on .get_display_name --- telethon/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/telethon/utils.py b/telethon/utils.py index 9f22794b..917eb85b 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -15,9 +15,14 @@ def get_display_name(entity): """Gets the input peer for the given "entity" (user, chat or channel) Returns None if it was not found""" if isinstance(entity, User): - if entity.last_name is not None: + if entity.last_name and entity.first_name: return '{} {}'.format(entity.first_name, entity.last_name) - return entity.first_name + elif entity.first_name: + return entity.first_name + elif entity.last_name: + return entity.last_name + else: + return '(No name)' if isinstance(entity, Chat) or isinstance(entity, Channel): return entity.title From 279ee81bc3330e157b67f98b98e5c81df44024a2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 16 Jun 2017 09:34:09 +0200 Subject: [PATCH 07/19] Stop querying terminal size and other warnings (#115) --- telethon_examples/interactive_telegram_client.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/telethon_examples/interactive_telegram_client.py b/telethon_examples/interactive_telegram_client.py index 748f4142..d73a34e1 100644 --- a/telethon_examples/interactive_telegram_client.py +++ b/telethon_examples/interactive_telegram_client.py @@ -1,4 +1,3 @@ -import shutil from getpass import getpass from telethon import TelegramClient @@ -6,9 +5,6 @@ from telethon.errors import SessionPasswordNeededError from telethon.tl.types import UpdateShortChatMessage, UpdateShortMessage from telethon.utils import get_display_name -# Get the (current) number of lines in the terminal -cols, rows = shutil.get_terminal_size() - def sprint(string, *args, **kwargs): """Safe Print (handle UnicodeEncodeErrors on some terminals)""" @@ -47,7 +43,8 @@ class InteractiveTelegramClient(TelegramClient): Telegram through Telethon, such as listing dialogs (open chats), talking to people, downloading media, and receiving updates. """ - def __init__(self, session_user_id, user_phone, api_id, api_hash, proxy=None): + def __init__(self, session_user_id, user_phone, api_id, api_hash, + proxy=None): print_title('Initialization') print('Initializing interactive example...') @@ -76,7 +73,7 @@ class InteractiveTelegramClient(TelegramClient): self_user = self.sign_in(user_phone, code) # Two-step verification may be enabled - except SessionPasswordNeededError as e: + except SessionPasswordNeededError: pw = getpass('Two step verification is enabled. ' 'Please enter your password: ') @@ -120,7 +117,7 @@ class InteractiveTelegramClient(TelegramClient): try: i = int(i if i else 0) - 1 - # Ensure it is inside the bounds, otherwise set to None and retry + # Ensure it is inside the bounds, otherwise retry if not 0 <= i < dialog_count: i = None except ValueError: From e7b0c06ca503bb86ccfff5e125d782925e374ab5 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 16 Jun 2017 10:01:05 +0200 Subject: [PATCH 08/19] Make .get_input_user faster when the right type is given --- telethon/utils.py | 4 +--- telethon_generator/tl_generator.py | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/telethon/utils.py b/telethon/utils.py index 917eb85b..d94734a2 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -52,9 +52,7 @@ def get_extension(media): def get_input_peer(entity): """Gets the input peer for the given "entity" (user, chat or channel). A ValueError is raised if the given entity isn't a supported type.""" - if any(isinstance(entity, c) for c in ( - InputPeerUser, InputPeerChat, InputPeerChannel, - InputPeerSelf, InputPeerEmpty)): + if type(entity).subclass_of_id == 0xc91c90b6: # crc32('InputUser') return entity if isinstance(entity, User): diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index 042c29dc..8ccc4d65 100755 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -2,6 +2,7 @@ import os import re import shutil +from zlib import crc32 from collections import defaultdict try: @@ -214,6 +215,9 @@ class TLGenerator: # Class-level variable to store its constructor ID builder.writeln("# Telegram's constructor (U)ID for this class") builder.writeln('constructor_id = {}'.format(hex(tlobject.id))) + builder.writeln("# Also the ID of its resulting type for fast checks") + builder.writeln('subclass_of_id = {}'.format( + hex(crc32(tlobject.result.encode('ascii'))))) builder.writeln() # Flag arguments must go last From 92b4125b2bf71537c32032e2171591f444369fa7 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 16 Jun 2017 10:11:03 +0200 Subject: [PATCH 09/19] Update to v0.11 --- setup.py | 1 + telethon/telegram_bare_client.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f3ec9173..dd755a32 100644 --- a/setup.py +++ b/setup.py @@ -60,6 +60,7 @@ setup( 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6' ], # What does your project relate to? diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 312e4835..9e28075e 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -47,7 +47,7 @@ class TelegramBareClient: """ # Current TelegramClient version - __version__ = '0.10.1' + __version__ = '0.11' # region Initialization From c304ee903f0b990fbc40ee91d7f943e15db937a6 Mon Sep 17 00:00:00 2001 From: Goblenus Date: Fri, 16 Jun 2017 15:59:10 +0300 Subject: [PATCH 10/19] Trigger reconnection on BrokenPipeError and InvalidChecksumError --- telethon/telegram_client.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index d49b5855..0644d898 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -11,7 +11,7 @@ from .errors import (RPCError, UnauthorizedError, InvalidParameterError, ReadCancelledError, FileMigrateError, PhoneMigrateError, NetworkMigrateError, UserMigrateError, PhoneCodeEmptyError, PhoneCodeExpiredError, PhoneCodeHashEmptyError, - PhoneCodeInvalidError) + PhoneCodeInvalidError, InvalidChecksumError) # For sending and receiving requests from .tl import MTProtoRequest, Session, JsonSession @@ -817,6 +817,14 @@ class TelegramClient(TelegramBareClient): except ReadCancelledError: self._logger.info('Receiving updates cancelled') + except BrokenPipeError: + self._logger.info('Tcp session is broken. Reconnecting...') + self.reconnect() + + except InvalidChecksumError: + self._logger.info('MTProto session is broken. Reconnecting...') + self.reconnect() + except OSError: self._logger.warning('OSError on updates thread, %s logging out', 'was' if self.sender.logging_out else 'was not') From 2b85463ce6438e07daf83c8febf685f9ffc3bb02 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 16 Jun 2017 15:36:47 +0200 Subject: [PATCH 11/19] Don't ignore more possible updates on .receive_updates() (closes #117) --- telethon/network/mtproto_sender.py | 8 +++++--- telethon/telegram_client.py | 16 +++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index d1ccca48..1301a863 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -113,11 +113,13 @@ class MtProtoSender: self._logger.info('Request result received') self._logger.debug('receive() released the lock') - def receive_update(self, timeout=timedelta(seconds=5)): - """Receives an update object and returns its result""" + def receive_updates(self, timeout=timedelta(seconds=5)): + """Receives one or more update objects + and returns them as a list + """ updates = [] self.receive(timeout=timeout, updates=updates) - return updates[0] + return updates def cancel_receive(self): """Cancels any pending receive operation diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 0644d898..2f1b7252 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -798,14 +798,20 @@ class TelegramClient(TelegramBareClient): self._logger.debug('Updates thread acquired the lock') try: self._updates_thread_receiving.set() - self._logger.debug('Trying to receive updates from the updates thread') + self._logger.debug( + 'Trying to receive updates from the updates thread' + ) - result = self.sender.receive_update(timeout=timeout) + updates = self.sender.receive_updates(timeout=timeout) self._updates_thread_receiving.clear() - self._logger.info('Received update from the updates thread') - for handler in self._update_handlers: - handler(result) + self._logger.info( + 'Received {} update(s) from the updates thread' + .format(len(updates)) + ) + for update in updates: + for handler in self._update_handlers: + handler(update) except ConnectionResetError: self._logger.info('Server disconnected us. Reconnecting...') From 9130a94153b7d9f70883da737fb60d41db73e09a Mon Sep 17 00:00:00 2001 From: zed Date: Sat, 17 Jun 2017 01:17:51 +0300 Subject: [PATCH 12/19] Support configuring SOCKS proxy in the example --- try_telethon.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/try_telethon.py b/try_telethon.py index 6793cdd2..74eb08c0 100755 --- a/try_telethon.py +++ b/try_telethon.py @@ -24,11 +24,18 @@ def load_settings(path='api/settings'): if __name__ == '__main__': # Load the settings and initialize the client settings = load_settings() + kwargs = {} + if settings.get('socks_proxy'): + import socks # $ pip install pysocks + host, port = settings['socks_proxy'].split(':') + kwargs = dict(proxy=(socks.SOCKS5, host, int(port))) + client = InteractiveTelegramClient( session_user_id=str(settings.get('session_name', 'anonymous')), user_phone=str(settings['user_phone']), api_id=settings['api_id'], - api_hash=str(settings['api_hash'])) + api_hash=str(settings['api_hash']), + **kwargs) print('Initialization done!') From 74a851b5cc261e85346c9e4bbbeeaf0950802a6c Mon Sep 17 00:00:00 2001 From: zed Date: Sat, 17 Jun 2017 01:18:31 +0300 Subject: [PATCH 13/19] Micro code style improvement (#125) --- telethon_examples/interactive_telegram_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/telethon_examples/interactive_telegram_client.py b/telethon_examples/interactive_telegram_client.py index d73a34e1..f4c6ba8a 100644 --- a/telethon_examples/interactive_telegram_client.py +++ b/telethon_examples/interactive_telegram_client.py @@ -97,8 +97,7 @@ class InteractiveTelegramClient(TelegramClient): print_title('Dialogs window') # Display them so the user can choose - for i, entity in enumerate(entities): - i += 1 # 1-based index + for i, entity in enumerate(entities, start=1): sprint('{}. {}'.format(i, get_display_name(entity))) # Let the user decide who they want to talk to From be33ae4e800619e0c50ef9dd7ce5e135e2ebb54b Mon Sep 17 00:00:00 2001 From: Lonami Date: Sat, 17 Jun 2017 08:25:48 +0200 Subject: [PATCH 14/19] Fix rpc_message_to_error failing to construct them --- telethon/errors/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telethon/errors/__init__.py b/telethon/errors/__init__.py index 7e781968..08ccee20 100644 --- a/telethon/errors/__init__.py +++ b/telethon/errors/__init__.py @@ -32,12 +32,12 @@ def rpc_message_to_error(code, message): return cls(extra=extra) elif code == 403: - return ForbiddenError() + return ForbiddenError(message) elif code == 404: - return NotFoundError() + return NotFoundError(message) elif code == 500: - return ServerError() + return ServerError(message) return RPCError('{} (code {})'.format(message, code)) From 765ae870cf5799e9672a198b70ee9e5828d93958 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 17 Jun 2017 22:04:29 +0200 Subject: [PATCH 15/19] Fix connection parameters not being copied on reconnection (#129) --- telethon/telegram_client.py | 5 ++++- telethon/tl/session.py | 26 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 2f1b7252..09d0092e 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -181,7 +181,10 @@ class TelegramClient(TelegramBareClient): # Create a temporary session for this IP address, which needs # to be different because each auth_key is unique per DC. - session = JsonSession(None) + # + # Construct this session with the connection parameters + # (system version, device model...) from the current one. + session = JsonSession(self.session) session.server_address = dc.ip_address session.port = dc.port client = TelegramBareClient(session, self.api_id, self.api_hash) diff --git a/telethon/tl/session.py b/telethon/tl/session.py index 3f42f51c..da0453b9 100644 --- a/telethon/tl/session.py +++ b/telethon/tl/session.py @@ -99,14 +99,28 @@ class JsonSession: through an official Telegram client to revoke the authorization. """ def __init__(self, session_user_id): + """session_user_id should either be a string or another Session. + Note that if another session is given, only parameters like + those required to init a connection will be copied. + """ # These values will NOT be saved - self.session_user_id = session_user_id + if isinstance(session_user_id, str): + self.session_user_id = session_user_id - # For connection purposes - self.device_model = platform.node() - self.system_version = platform.system() - self.app_version = '0' - self.lang_code = 'en' + # For connection purposes + self.device_model = platform.node() + self.system_version = platform.system() + self.app_version = '0' + self.lang_code = 'en' + + elif isinstance(session_user_id, JsonSession): + self.session_user_id = None + + session = session_user_id + self.device_model = session.device_model + self.system_version = session.system_version + self.app_version = session.app_version + self.lang_code = session.lang_code # Cross-thread safety self._lock = Lock() From 8afb0a3f6b33f69d84ff7f4f6354311217d75375 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 18 Jun 2017 10:01:59 +0200 Subject: [PATCH 16/19] Rename Request.msg_id to request_msg_id to avoid name clash (fix #122) --- telethon/network/mtproto_sender.py | 12 ++++++------ telethon/tl/mtproto_request.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 1301a863..516a381c 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -133,13 +133,13 @@ class MtProtoSender: def _send_packet(self, packet, request): """Sends the given packet bytes with the additional information of the original request. This does NOT lock the threads!""" - request.msg_id = self.session.get_new_msg_id() + request.request_msg_id = self.session.get_new_msg_id() # First calculate plain_text to encrypt it with BinaryWriter() as plain_writer: plain_writer.write_long(self.session.salt, signed=False) plain_writer.write_long(self.session.id, signed=False) - plain_writer.write_long(request.msg_id) + plain_writer.write_long(request.request_msg_id) plain_writer.write_int( self.session.generate_sequence(request.confirmed)) @@ -223,7 +223,7 @@ class MtProtoSender: if code == 0x62d6b459: ack = reader.tgread_object() for r in self._pending_receive: - if r.msg_id in ack.msg_ids: + if r.request_msg_id in ack.msg_ids: self._logger.warning('Ack found for the a request') if self.logging_out: @@ -259,7 +259,7 @@ class MtProtoSender: try: request = next(r for r in self._pending_receive - if r.msg_id == received_msg_id) + if r.request_msg_id == received_msg_id) self._logger.warning('Pong confirmed a request') request.confirm_received = True @@ -296,7 +296,7 @@ class MtProtoSender: try: request = next(r for r in self._pending_receive - if r.msg_id == bad_msg_id) + if r.request_msg_id == bad_msg_id) self.send(request) except StopIteration: pass @@ -330,7 +330,7 @@ class MtProtoSender: try: request = next(r for r in self._pending_receive - if r.msg_id == request_id) + if r.request_msg_id == request_id) request.confirm_received = True except StopIteration: diff --git a/telethon/tl/mtproto_request.py b/telethon/tl/mtproto_request.py index 3c319d8c..391eeb07 100644 --- a/telethon/tl/mtproto_request.py +++ b/telethon/tl/mtproto_request.py @@ -5,7 +5,7 @@ class MTProtoRequest: def __init__(self): self.sent = False - self.msg_id = 0 # Long + self.request_msg_id = 0 # Long self.sequence = 0 self.dirty = False From c13164f5cfe878057e8ceb77d5abfda495d2b8d2 Mon Sep 17 00:00:00 2001 From: Hasan Date: Sun, 18 Jun 2017 17:38:14 -0400 Subject: [PATCH 17/19] Use the correct amount of random bytes in DH request The official documentation says a 2048 *bit* number. `os.urandom` takes an argument that represents the number of *bytes*. 2048 bits is 256 bytes --- telethon/network/authenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index d13dfc9a..52971ffb 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -148,7 +148,7 @@ def do_authentication(transport): server_time = dh_inner_data_reader.read_int() time_offset = server_time - int(time.time()) - b = get_int(os.urandom(2048), signed=False) + b = get_int(os.urandom(256), signed=False) gb = pow(g, b, dh_prime) gab = pow(ga, b, dh_prime) From 2af962230f3642f2821dba9a8b0b2805246d7ac9 Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Mon, 19 Jun 2017 07:20:36 +1000 Subject: [PATCH 18/19] Fix error 400:CONNECTION_APP_VERSION_EMPTY when using temporary sessions --- telethon/tl/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/tl/session.py b/telethon/tl/session.py index da0453b9..dc1b13ed 100644 --- a/telethon/tl/session.py +++ b/telethon/tl/session.py @@ -110,7 +110,7 @@ class JsonSession: # For connection purposes self.device_model = platform.node() self.system_version = platform.system() - self.app_version = '0' + self.app_version = '1.0' # note: '0' will provoke error self.lang_code = 'en' elif isinstance(session_user_id, JsonSession): From 8d9e50989b6b647ce1b32db28a2ed6cbe72f66c7 Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Mon, 19 Jun 2017 07:34:23 +1000 Subject: [PATCH 19/19] Fix temporary Session fields not being defined --- telethon/tl/session.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/telethon/tl/session.py b/telethon/tl/session.py index dc1b13ed..ee5f1091 100644 --- a/telethon/tl/session.py +++ b/telethon/tl/session.py @@ -104,24 +104,24 @@ class JsonSession: those required to init a connection will be copied. """ # These values will NOT be saved - if isinstance(session_user_id, str): - self.session_user_id = session_user_id - - # For connection purposes - self.device_model = platform.node() - self.system_version = platform.system() - self.app_version = '1.0' # note: '0' will provoke error - self.lang_code = 'en' - - elif isinstance(session_user_id, JsonSession): + if isinstance(session_user_id, JsonSession): self.session_user_id = None + # For connection purposes session = session_user_id self.device_model = session.device_model self.system_version = session.system_version self.app_version = session.app_version self.lang_code = session.lang_code + else: # str / None + self.session_user_id = session_user_id + + self.device_model = platform.node() + self.system_version = platform.system() + self.app_version = '1.0' # note: '0' will provoke error + self.lang_code = 'en' + # Cross-thread safety self._lock = Lock()