From b252468ca293b1a72fc7af4184533b556e84df18 Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Thu, 28 Dec 2017 07:50:49 +1000 Subject: [PATCH 1/4] TelegramBareClient: Add set_proxy() method This allows to change proxy without recreation of the client instance. --- telethon/telegram_bare_client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 27acfe9a..f22d13e6 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -299,6 +299,13 @@ class TelegramBareClient: self.disconnect() return self.connect() + def set_proxy(self, proxy): + """Change the proxy used by the connections. + """ + if self.is_connected(): + raise RuntimeError("You can't change the proxy while connected.") + self._sender.connection.conn.proxy = proxy + # endregion # region Working with different connections/Data Centers From 166d5a401237ee56eb6e14a26dea8cb66648bb3f Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 27 Dec 2017 23:45:48 +0100 Subject: [PATCH 2/4] Fix .get_dialogs() being inconsistent with the return type --- telethon/telegram_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 792ccd06..d8000b3b 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -301,7 +301,7 @@ class TelegramClient(TelegramBareClient): """ limit = float('inf') if limit is None else int(limit) if limit == 0: - return [], [] + return [] dialogs = OrderedDict() # Use peer id as identifier to avoid dupes while len(dialogs) < limit: From 1a746e14643a91ae33d186a383d1cfdc36433081 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 27 Dec 2017 23:54:31 +0100 Subject: [PATCH 3/4] Fix .download_profile_photo() for some channels (closes #500) --- telethon/telegram_client.py | 50 +++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index d8000b3b..e0708bc9 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -14,7 +14,8 @@ from . import TelegramBareClient from . import helpers, utils from .errors import ( RPCError, UnauthorizedError, InvalidParameterError, PhoneCodeEmptyError, - PhoneCodeExpiredError, PhoneCodeHashEmptyError, PhoneCodeInvalidError + PhoneCodeExpiredError, PhoneCodeHashEmptyError, PhoneCodeInvalidError, + LocationInvalidError ) from .network import ConnectionMode from .tl import TLObject @@ -43,7 +44,7 @@ from .tl.functions.users import ( GetUsersRequest ) from .tl.functions.channels import ( - GetChannelsRequest + GetChannelsRequest, GetFullChannelRequest ) from .tl.types import ( DocumentAttributeAudio, DocumentAttributeFilename, @@ -744,6 +745,7 @@ 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 = [] if not isinstance(entity, TLObject) or type(entity).SUBCLASS_OF_ID in ( 0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697 @@ -769,31 +771,41 @@ class TelegramClient(TelegramBareClient): for attr in ('username', 'first_name', 'title'): possible_names.append(getattr(entity, attr, None)) - entity = entity.photo + photo = entity.photo - if not isinstance(entity, UserProfilePhoto) and \ - not isinstance(entity, ChatPhoto): + if not isinstance(photo, UserProfilePhoto) and \ + not isinstance(photo, ChatPhoto): return None - if download_big: - photo_location = entity.photo_big - else: - photo_location = entity.photo_small - + 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 - self.download_file( - InputFileLocation( - volume_id=photo_location.volume_id, - local_id=photo_location.local_id, - secret=photo_location.secret - ), - file - ) + try: + self.download_file( + InputFileLocation( + volume_id=photo_location.volume_id, + local_id=photo_location.local_id, + secret=photo_location.secret + ), + 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. + ie = self.get_input_entity(entity) + if isinstance(ie, InputPeerChannel): + full = self(GetFullChannelRequest(ie)) + return self._download_photo( + full.full_chat.chat_photo, file, + date=None, progress_callback=None + ) + 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): @@ -833,7 +845,7 @@ class TelegramClient(TelegramBareClient): """Specialized version of .download_media() for photos""" # Determine the photo and its largest size - photo = mm_photo.photo + photo = getattr(mm_photo, 'photo', mm_photo) largest_size = photo.sizes[-1] file_size = largest_size.size largest_size = largest_size.location From 6ec6967ff9a2e09aae70b500273075bdfbae975c Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Thu, 28 Dec 2017 09:22:28 +1000 Subject: [PATCH 4/4] Make exception types correspond to Python docs --- docs/docs_writer.py | 2 +- telethon/errors/__init__.py | 5 ++--- telethon/errors/common.py | 7 ------- telethon/extensions/binary_reader.py | 9 ++++----- telethon/extensions/tcp_client.py | 2 +- telethon/telegram_bare_client.py | 16 ++++++++-------- telethon/telegram_client.py | 15 +++++++-------- telethon/tl/custom/draft.py | 2 +- telethon/tl/tlobject.py | 3 ++- telethon/utils.py | 6 +++--- 10 files changed, 29 insertions(+), 38 deletions(-) diff --git a/docs/docs_writer.py b/docs/docs_writer.py index f9042f00..9eec6cd7 100644 --- a/docs/docs_writer.py +++ b/docs/docs_writer.py @@ -90,7 +90,7 @@ class DocsWriter: def end_menu(self): """Ends an opened menu""" if not self.menu_began: - raise ValueError('No menu had been started in the first place.') + raise RuntimeError('No menu had been started in the first place.') self.write('') def write_title(self, title, level=1): diff --git a/telethon/errors/__init__.py b/telethon/errors/__init__.py index fbb2f424..9126aca3 100644 --- a/telethon/errors/__init__.py +++ b/telethon/errors/__init__.py @@ -7,9 +7,8 @@ import re from threading import Thread from .common import ( - ReadCancelledError, InvalidParameterError, TypeNotFoundError, - InvalidChecksumError, BrokenAuthKeyError, SecurityError, - CdnFileTamperedError + ReadCancelledError, TypeNotFoundError, InvalidChecksumError, + BrokenAuthKeyError, SecurityError, CdnFileTamperedError ) # This imports the base errors too, as they're imported there diff --git a/telethon/errors/common.py b/telethon/errors/common.py index f2f21840..46b0b52e 100644 --- a/telethon/errors/common.py +++ b/telethon/errors/common.py @@ -7,13 +7,6 @@ class ReadCancelledError(Exception): super().__init__(self, 'The read operation was cancelled.') -class InvalidParameterError(Exception): - """ - Occurs when an invalid parameter is given, for example, - when either A or B are required but none is given. - """ - - class TypeNotFoundError(Exception): """ Occurs when a type is not found, for example, diff --git a/telethon/extensions/binary_reader.py b/telethon/extensions/binary_reader.py index 19fb608b..460bed96 100644 --- a/telethon/extensions/binary_reader.py +++ b/telethon/extensions/binary_reader.py @@ -6,7 +6,7 @@ from datetime import datetime from io import BufferedReader, BytesIO from struct import unpack -from ..errors import InvalidParameterError, TypeNotFoundError +from ..errors import TypeNotFoundError from ..tl.all_tlobjects import tlobjects @@ -22,8 +22,7 @@ class BinaryReader: elif stream: self.stream = stream else: - raise InvalidParameterError( - 'Either bytes or a stream must be provided') + raise ValueError('Either bytes or a stream must be provided') self.reader = BufferedReader(self.stream) self._last = None # Should come in handy to spot -404 errors @@ -110,7 +109,7 @@ class BinaryReader: elif value == 0xbc799737: # boolFalse return False else: - raise ValueError('Invalid boolean code {}'.format(hex(value))) + raise RuntimeError('Invalid boolean code {}'.format(hex(value))) def tgread_date(self): """Reads and converts Unix time (used by Telegram) @@ -141,7 +140,7 @@ class BinaryReader: def tgread_vector(self): """Reads a vector (a list) of Telegram objects.""" if 0x1cb5c415 != self.read_int(signed=False): - raise ValueError('Invalid constructor code, vector was expected') + raise RuntimeError('Invalid constructor code, vector was expected') count = self.read_int() return [self.tgread_object() for _ in range(count)] diff --git a/telethon/extensions/tcp_client.py b/telethon/extensions/tcp_client.py index f59bb9f0..61be30f5 100644 --- a/telethon/extensions/tcp_client.py +++ b/telethon/extensions/tcp_client.py @@ -26,7 +26,7 @@ class TcpClient: elif isinstance(timeout, (int, float)): self.timeout = float(timeout) else: - raise ValueError('Invalid timeout type', type(timeout)) + raise TypeError('Invalid timeout type: {}'.format(type(timeout))) def _recreate_socket(self, mode): if self.proxy is None: diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index f22d13e6..36820629 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -84,7 +84,7 @@ class TelegramBareClient: **kwargs): """Refer to TelegramClient.__init__ for docs on this method""" if not api_id or not api_hash: - raise PermissionError( + raise ValueError( "Your API ID or Hash cannot be empty or None. " "Refer to Telethon's wiki for more information.") @@ -94,7 +94,7 @@ class TelegramBareClient: if isinstance(session, str) or session is None: session = Session.try_load_or_create_new(session) elif not isinstance(session, Session): - raise ValueError( + raise TypeError( 'The given session must be a str or a Session instance.' ) @@ -421,11 +421,11 @@ class TelegramBareClient: """Invokes (sends) a MTProtoRequest and returns (receives) its result. The invoke will be retried up to 'retries' times before raising - ValueError(). + RuntimeError(). """ if not all(isinstance(x, TLObject) and x.content_related for x in requests): - raise ValueError('You can only invoke requests, not types!') + raise TypeError('You can only invoke requests, not types!') # For logging purposes if len(requests) == 1: @@ -486,7 +486,7 @@ class TelegramBareClient: else: sender.connect() - raise ValueError('Number of retries reached 0.') + raise RuntimeError('Number of retries reached 0.') finally: if sender != self._sender: sender.disconnect() # Close temporary connections @@ -682,8 +682,8 @@ class TelegramBareClient: if progress_callback: progress_callback(stream.tell(), file_size) else: - raise ValueError('Failed to upload file part {}.' - .format(part_index)) + raise RuntimeError( + 'Failed to upload file part {}.'.format(part_index)) finally: stream.close() @@ -853,7 +853,7 @@ class TelegramBareClient: :return: """ if self._spawn_read_thread and not self._on_read_thread(): - raise ValueError('Can only idle if spawn_read_thread=False') + raise RuntimeError('Can only idle if spawn_read_thread=False') for sig in stop_signals: signal(sig, self._signal_handler) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index e0708bc9..11d677ae 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -13,9 +13,8 @@ except ImportError: from . import TelegramBareClient from . import helpers, utils from .errors import ( - RPCError, UnauthorizedError, InvalidParameterError, PhoneCodeEmptyError, - PhoneCodeExpiredError, PhoneCodeHashEmptyError, PhoneCodeInvalidError, - LocationInvalidError + RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError, + PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError ) from .network import ConnectionMode from .tl import TLObject @@ -381,7 +380,7 @@ class TelegramClient(TelegramBareClient): if parse_mode in {'md', 'markdown'}: message, msg_entities = markdown.parse(message) else: - raise ValueError('Unknown parsing mode', parse_mode) + raise ValueError('Unknown parsing mode: {}'.format(parse_mode)) else: msg_entities = [] @@ -572,7 +571,7 @@ class TelegramClient(TelegramBareClient): """ if max_id is None: if not messages: - raise InvalidParameterError( + raise ValueError( 'Either a message list or a max_id must be provided.') if hasattr(message, '__iter__'): @@ -600,7 +599,7 @@ class TelegramClient(TelegramBareClient): # hex(crc32(b'Message')) = 0x790009e3 return reply_to.id - raise ValueError('Invalid reply_to type: ', type(reply_to)) + raise TypeError('Invalid reply_to type: {}'.format(type(reply_to))) # endregion @@ -1053,7 +1052,7 @@ class TelegramClient(TelegramBareClient): if isinstance(entity, str): return self._get_entity_from_string(entity) - raise ValueError( + raise TypeError( 'Cannot turn "{}" into any entity (user or chat)'.format(entity) ) @@ -1128,7 +1127,7 @@ class TelegramClient(TelegramBareClient): pass if not is_peer: - raise ValueError( + raise TypeError( 'Cannot turn "{}" into an input entity.'.format(peer) ) diff --git a/telethon/tl/custom/draft.py b/telethon/tl/custom/draft.py index c50baa78..abf84548 100644 --- a/telethon/tl/custom/draft.py +++ b/telethon/tl/custom/draft.py @@ -21,7 +21,7 @@ class Draft: @classmethod def _from_update(cls, client, update): if not isinstance(update, UpdateDraftMessage): - raise ValueError( + raise TypeError( 'You can only create a new `Draft` from a corresponding ' '`UpdateDraftMessage` object.' ) diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index e2b23018..489765e2 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -97,7 +97,8 @@ class TLObject: if isinstance(data, str): data = data.encode('utf-8') else: - raise ValueError('bytes or str expected, not', type(data)) + raise TypeError( + 'bytes or str expected, not {}'.format(type(data))) r = [] if len(data) < 254: diff --git a/telethon/utils.py b/telethon/utils.py index 5e92b13d..388af83e 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -67,13 +67,13 @@ def get_extension(media): def _raise_cast_fail(entity, target): - raise ValueError('Cannot cast {} to any kind of {}.' - .format(type(entity).__name__, target)) + raise TypeError('Cannot cast {} to any kind of {}.'.format( + type(entity).__name__, target)) def get_input_peer(entity, allow_self=True): """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.""" + A TypeError is raised if the given entity isn't a supported type.""" if not isinstance(entity, TLObject): _raise_cast_fail(entity, 'InputPeer')