From 1b71c6fbf162b85522c82661685f3664376fc4ec Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Mon, 16 Oct 2017 20:19:16 +0300 Subject: [PATCH 01/23] Fix vector regex in parser (#347) --- telethon_generator/parser/tl_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon_generator/parser/tl_object.py b/telethon_generator/parser/tl_object.py index 416bc587..51fe3d24 100644 --- a/telethon_generator/parser/tl_object.py +++ b/telethon_generator/parser/tl_object.py @@ -222,7 +222,7 @@ class TLArg: self.type = flag_match.group(2) # Then check if the type is a Vector - vector_match = re.match(r'vector<(\w+)>', self.type, re.IGNORECASE) + vector_match = re.match(r'[Vv]ector<([\w\d.]+)>', self.type) if vector_match: self.is_vector = True From ee01724cdb7027c1e38625d31446ba1ea7bade92 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Mon, 16 Oct 2017 21:15:22 +0300 Subject: [PATCH 02/23] Fix parsing for constructors and not objects (#348) --- telethon_generator/parser/tl_object.py | 15 +++++++++++++-- telethon_generator/tl_generator.py | 11 +++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/telethon_generator/parser/tl_object.py b/telethon_generator/parser/tl_object.py index 51fe3d24..79b4385d 100644 --- a/telethon_generator/parser/tl_object.py +++ b/telethon_generator/parser/tl_object.py @@ -98,12 +98,17 @@ class TLObject: def class_name(self): """Gets the class name following the Python style guidelines""" + return self.class_name_for(self.name, self.is_function) + @staticmethod + def class_name_for(typename, is_function=False): + """Gets the class name following the Python style guidelines""" # Courtesy of http://stackoverflow.com/a/31531797/4759433 - result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), self.name) + result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), + typename) result = result[:1].upper() + result[1:].replace('_', '') # If it's a function, let it end with "Request" to identify them - if self.is_function: + if is_function: result += 'Request' return result @@ -192,6 +197,7 @@ class TLArg: # Default values self.is_vector = False self.is_flag = False + self.skip_constructor_id = False self.flag_index = -1 # Special case: some types can be inferred, which makes it @@ -234,6 +240,11 @@ class TLArg: # Update the type to match the one inside the vector self.type = vector_match.group(1) + # See use_vector_id. An example of such case is ipPort in + # help.configSpecial + if self.type.split('.')[-1][0].islower(): + self.skip_constructor_id = True + # The name may contain "date" in it, if this is the case and the type is "int", # we can safely assume that this should be treated as a "date" object. # Note that this is not a valid Telegram object, but it's easier to work with diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index 0e4f0013..754866bb 100644 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -5,7 +5,7 @@ import struct from zlib import crc32 from collections import defaultdict -from .parser import SourceBuilder, TLParser +from .parser import SourceBuilder, TLParser, TLObject AUTO_GEN_NOTICE = \ '"""File generated by TLObjects\' generator. All changes will be ERASED"""' @@ -129,6 +129,9 @@ class TLGenerator: builder.writeln( 'from {}.tl.tlobject import TLObject'.format('.' * depth) ) + builder.writeln( + 'from {}.tl import types'.format('.' * depth) + ) # Add the relative imports to the namespaces, # unless we already are in a namespace. @@ -638,7 +641,11 @@ class TLGenerator: else: # Else it may be a custom type - builder.writeln('{} = reader.tgread_object()'.format(name)) + if not arg.skip_constructor_id: + builder.writeln('{} = reader.tgread_object()'.format(name)) + else: + builder.writeln('{} = types.{}.from_reader(reader)'.format( + name, TLObject.class_name_for(arg.type))) # End vector and flag blocks if required (if we opened them before) if arg.is_vector: From ed77ba6f8ff115ac624f02f691c9991e5b37be60 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Tue, 17 Oct 2017 01:39:04 +0300 Subject: [PATCH 03/23] Likely fix .log_out crashing "calling Event" (#349) --- telethon/network/mtproto_sender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index aa9c0a74..520cadbf 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -217,7 +217,7 @@ class MtProtoSender: r = self._pop_request_of_type(msg_id, LogOutRequest) if r: r.result = True # Telegram won't send this value - r.confirm_received() + r.confirm_received.set() self._logger.debug('Message ack confirmed', r) return True From 63dfb1e3ead5335e05bf30f96bfba146e5f529d8 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 17 Oct 2017 10:15:13 +0200 Subject: [PATCH 04/23] Fix processing messages pending of acknowledge many times --- telethon/network/mtproto_sender.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 520cadbf..16a82bbd 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -174,6 +174,15 @@ class MtProtoSender: """ # TODO Check salt, session_id and sequence_number + if msg_id in self._need_confirmation: + # We're yet to acknowledge this message already, so just drop it + # as we are already aware of it. TODO Should we force acknowledging + # all the self._need_confirmation IDs? + self._logger.debug( + 'Ignoring message pending of acknowledge: {}'.format(msg_id) + ) + return False + self._need_confirmation.append(msg_id) code = reader.read_int(signed=False) From adb79b21cf08ae876ed70e2decb919051cce00c5 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 17 Oct 2017 19:54:24 +0200 Subject: [PATCH 05/23] Replace .to_bytes() with the special .__bytes__ function --- telethon/network/authenticator.py | 14 +++++++------- telethon/network/mtproto_sender.py | 2 +- telethon/tl/gzip_packed.py | 8 ++++---- telethon/tl/message_container.py | 4 ++-- telethon/tl/tl_message.py | 2 +- telethon/tl/tlobject.py | 2 +- telethon_generator/tl_generator.py | 12 ++++++------ 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index 78df5d87..1accf493 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -42,7 +42,7 @@ def _do_authentication(connection): req_pq_request = ReqPqRequest( nonce=int.from_bytes(os.urandom(16), 'big', signed=True) ) - sender.send(req_pq_request.to_bytes()) + sender.send(bytes(req_pq_request)) with BinaryReader(sender.receive()) as reader: req_pq_request.on_response(reader) @@ -60,12 +60,12 @@ def _do_authentication(connection): p, q = rsa.get_byte_array(min(p, q)), rsa.get_byte_array(max(p, q)) new_nonce = int.from_bytes(os.urandom(32), 'little', signed=True) - pq_inner_data = PQInnerData( + pq_inner_data = bytes(PQInnerData( pq=rsa.get_byte_array(pq), p=p, q=q, nonce=res_pq.nonce, server_nonce=res_pq.server_nonce, new_nonce=new_nonce - ).to_bytes() + )) # sha_digest + data + random_bytes cipher_text, target_fingerprint = None, None @@ -90,7 +90,7 @@ def _do_authentication(connection): public_key_fingerprint=target_fingerprint, encrypted_data=cipher_text ) - sender.send(req_dh_params.to_bytes()) + sender.send(bytes(req_dh_params)) # Step 2 response: DH Exchange with BinaryReader(sender.receive()) as reader: @@ -138,12 +138,12 @@ def _do_authentication(connection): gab = pow(g_a, b, dh_prime) # Prepare client DH Inner Data - client_dh_inner = ClientDHInnerData( + client_dh_inner = bytes(ClientDHInnerData( nonce=res_pq.nonce, server_nonce=res_pq.server_nonce, retry_id=0, # TODO Actual retry ID g_b=rsa.get_byte_array(gb) - ).to_bytes() + )) client_dh_inner_hashed = sha1(client_dh_inner).digest() + client_dh_inner @@ -156,7 +156,7 @@ def _do_authentication(connection): server_nonce=res_pq.server_nonce, encrypted_data=client_dh_encrypted, ) - sender.send(set_client_dh.to_bytes()) + sender.send(bytes(set_client_dh)) # Step 3 response: Complete DH Exchange with BinaryReader(sender.receive()) as reader: diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 16a82bbd..fab8bebd 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -125,7 +125,7 @@ class MtProtoSender: plain_text = \ struct.pack(' 512: - gzipped = GzipPacked(data).to_bytes() + gzipped = bytes(GzipPacked(data)) return gzipped if len(gzipped) < len(data) else data else: return data - def to_bytes(self): + def __bytes__(self): # TODO Maybe compress level could be an option return struct.pack(' Date: Wed, 18 Oct 2017 12:17:13 +0200 Subject: [PATCH 06/23] Avoid calling .sync_updates when not needed --- telethon/telegram_bare_client.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 98ddf6d0..b98dbeea 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -132,7 +132,7 @@ class TelegramBareClient: self._user_connected = False # Save whether the user is authorized here (a.k.a. logged in) - self._authorized = False + self._authorized = None # None = We don't know yet # Uploaded files cache so subsequent calls are instant self._upload_cache = {} @@ -223,7 +223,7 @@ class TelegramBareClient: # another data center and this would raise UserMigrateError) # to also assert whether the user is logged in or not. self._user_connected = True - if _sync_updates and not _cdn: + if self._authorized is None and _sync_updates and not _cdn: try: self.sync_updates() self._set_connected_and_authorized() @@ -749,10 +749,7 @@ class TelegramBareClient: if not self.updates.get_workers: warnings.warn("There are no update workers running, so adding an update handler will have no effect.") - sync = not self.updates.handlers self.updates.handlers.append(handler) - if sync: - self.sync_updates() def remove_update_handler(self, handler): self.updates.handlers.remove(handler) From 87dc476daeb6a13a6e459f74d33ab2f6ff440ff6 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 18 Oct 2017 14:45:08 +0200 Subject: [PATCH 07/23] Fix ReadThread not starting on reconnect if already authorized --- telethon/telegram_bare_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index b98dbeea..65c63f4b 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -229,6 +229,8 @@ class TelegramBareClient: self._set_connected_and_authorized() except UnauthorizedError: self._authorized = False + elif self._authorized: + self._set_connected_and_authorized() return True From 16cf94c9add5e94d70c4eee2ac142d8e76af48b9 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Wed, 18 Oct 2017 15:47:03 +0300 Subject: [PATCH 08/23] Fix ._clear_all_pending failing due to a wrong call (#352) --- telethon/network/mtproto_sender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index fab8bebd..f948946f 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -270,7 +270,7 @@ class MtProtoSender: def _clear_all_pending(self): for r in self._pending_receive.values(): - r.confirm_received.set() + r.confirm_received.request.set() self._pending_receive.clear() def _handle_pong(self, msg_id, sequence, reader): From e349910eb9019a64dbf0874f114fdcbf0c955a81 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Wed, 18 Oct 2017 16:34:04 +0300 Subject: [PATCH 09/23] Fix attribute access order being swapped (#353) --- telethon/network/mtproto_sender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index f948946f..2e1f4627 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -270,7 +270,7 @@ class MtProtoSender: def _clear_all_pending(self): for r in self._pending_receive.values(): - r.confirm_received.request.set() + r.request.confirm_received.set() self._pending_receive.clear() def _handle_pong(self, msg_id, sequence, reader): From f49208f96179fb775e9d32c23bb2ba66bc6a5935 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 18 Oct 2017 20:43:46 +0200 Subject: [PATCH 10/23] Fix assert condition on generated code with flags involved The specific case was SendMessageRequest with InputMessageEntityMentionName, failing with bot/bot_info --- telethon_generator/tl_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index fde3cd22..5ee12969 100644 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -314,11 +314,11 @@ class TLGenerator: for ra in repeated_args.values(): if len(ra) > 1: - cnd1 = ('self.{} is None'.format(a.name) for a in ra) - cnd2 = ('self.{} is not None'.format(a.name) for a in ra) + cnd1 = ('self.{}'.format(a.name) for a in ra) + cnd2 = ('not self.{}'.format(a.name) for a in ra) builder.writeln( "assert ({}) or ({}), '{} parameters must all " - "be None or neither be None'".format( + "be False-y (like None) or all me True-y''".format( ' and '.join(cnd1), ' and '.join(cnd2), ', '.join(a.name for a in ra) ) From 0e1249c8337d1aa61a0e23875fe8fcfd370e4b95 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 19 Oct 2017 10:42:09 +0200 Subject: [PATCH 11/23] Fix incorrectly generated code --- telethon_generator/tl_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index 5ee12969..8fc6bb2d 100644 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -318,7 +318,7 @@ class TLGenerator: cnd2 = ('not self.{}'.format(a.name) for a in ra) builder.writeln( "assert ({}) or ({}), '{} parameters must all " - "be False-y (like None) or all me True-y''".format( + "be False-y (like None) or all me True-y'".format( ' and '.join(cnd1), ' and '.join(cnd2), ', '.join(a.name for a in ra) ) From 050cd95d324ba0f12fad90aaa564940a98312453 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Thu, 19 Oct 2017 11:51:34 +0300 Subject: [PATCH 12/23] Remove unnecessary .disconnect() from ._invoke() (#356) --- telethon/telegram_bare_client.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 65c63f4b..0b31c4c5 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -499,10 +499,7 @@ class TelegramBareClient: else: while self._user_connected and not self._reconnect(): sleep(0.1) # Retry forever until we can send the request - - finally: - if sender != self._sender: - sender.disconnect() + return None try: raise next(x.rpc_error for x in requests if x.rpc_error) From f37b9ed20eed6952357e8776b29217e547c21c8d Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Fri, 20 Oct 2017 17:48:54 +0300 Subject: [PATCH 13/23] Fix new salt not being saved to session file (#362) --- telethon/network/mtproto_sender.py | 1 + 1 file changed, 1 insertion(+) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 2e1f4627..61e60484 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -312,6 +312,7 @@ class MtProtoSender: self.session.salt = struct.unpack( ' Date: Fri, 20 Oct 2017 15:44:43 +0200 Subject: [PATCH 14/23] Generate errors from PWRTelegram's API --- .gitignore | 1 + telethon/errors/__init__.py | 45 +- .../{rpc_errors.py => rpc_base_errors.py} | 0 telethon/errors/rpc_errors_303.py | 51 -- telethon/errors/rpc_errors_400.py | 453 ------------------ telethon/errors/rpc_errors_401.py | 98 ---- telethon/errors/rpc_errors_420.py | 16 - telethon_generator/error_descriptions | 65 +++ telethon_generator/error_generator.py | 172 +++++++ telethon_generator/errors.json | 1 + 10 files changed, 260 insertions(+), 642 deletions(-) rename telethon/errors/{rpc_errors.py => rpc_base_errors.py} (100%) delete mode 100644 telethon/errors/rpc_errors_303.py delete mode 100644 telethon/errors/rpc_errors_400.py delete mode 100644 telethon/errors/rpc_errors_401.py delete mode 100644 telethon/errors/rpc_errors_420.py create mode 100644 telethon_generator/error_descriptions create mode 100644 telethon_generator/error_generator.py create mode 100644 telethon_generator/errors.json diff --git a/.gitignore b/.gitignore index aef0b91f..dd5de4c3 100755 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ telethon/tl/functions/ telethon/tl/types/ telethon/tl/all_tlobjects.py +telethon/tl/errors/rpc_error_list.py # User session *.session diff --git a/telethon/errors/__init__.py b/telethon/errors/__init__.py index d65d426c..6e62bfb9 100644 --- a/telethon/errors/__init__.py +++ b/telethon/errors/__init__.py @@ -8,15 +8,8 @@ from .common import ( CdnFileTamperedError ) -from .rpc_errors import ( - RPCError, InvalidDCError, BadRequestError, UnauthorizedError, - ForbiddenError, NotFoundError, FloodError, ServerError, BadMessageError -) - -from .rpc_errors_303 import * -from .rpc_errors_400 import * -from .rpc_errors_401 import * -from .rpc_errors_420 import * +# This imports the base errors too, as they're imported there +from .rpc_error_list import * def report_error(code, message, report_method): @@ -43,27 +36,31 @@ def rpc_message_to_error(code, message, report_method=None): args=(code, message, report_method) ).start() - errors = { - 303: rpc_errors_303_all, - 400: rpc_errors_400_all, - 401: rpc_errors_401_all, - 420: rpc_errors_420_all - }.get(code, None) + # Try to get the error by direct look-up, otherwise regex + # TODO Maybe regexes could live in a separate dictionary? + cls = rpc_errors_all.get(message, None) + if cls: + return cls() - if errors is not None: - for msg, cls in errors.items(): - m = re.match(msg, message) - if m: - extra = int(m.group(1)) if m.groups() else None - return cls(extra=extra) + for msg_regex, cls in rpc_errors_all.items(): + m = re.match(msg_regex, message) + if m: + capture = int(m.group(1)) if m.groups() else None + return cls(capture=capture) - elif code == 403: + if code == 400: + return BadRequestError(message) + + if code == 401: + return UnauthorizedError(message) + + if code == 403: return ForbiddenError(message) - elif code == 404: + if code == 404: return NotFoundError(message) - elif code == 500: + if code == 500: return ServerError(message) return RPCError('{} (code {})'.format(message, code)) diff --git a/telethon/errors/rpc_errors.py b/telethon/errors/rpc_base_errors.py similarity index 100% rename from telethon/errors/rpc_errors.py rename to telethon/errors/rpc_base_errors.py diff --git a/telethon/errors/rpc_errors_303.py b/telethon/errors/rpc_errors_303.py deleted file mode 100644 index 21963154..00000000 --- a/telethon/errors/rpc_errors_303.py +++ /dev/null @@ -1,51 +0,0 @@ -from . import InvalidDCError - - -class FileMigrateError(InvalidDCError): - def __init__(self, **kwargs): - self.new_dc = kwargs['extra'] - super(Exception, self).__init__( - self, - 'The file to be accessed is currently stored in DC {}.' - .format(self.new_dc) - ) - - -class PhoneMigrateError(InvalidDCError): - def __init__(self, **kwargs): - self.new_dc = kwargs['extra'] - super(Exception, self).__init__( - self, - 'The phone number a user is trying to use for authorization is ' - 'associated with DC {}.' - .format(self.new_dc) - ) - - -class NetworkMigrateError(InvalidDCError): - def __init__(self, **kwargs): - self.new_dc = kwargs['extra'] - super(Exception, self).__init__( - self, - 'The source IP address is associated with DC {}.' - .format(self.new_dc) - ) - - -class UserMigrateError(InvalidDCError): - def __init__(self, **kwargs): - self.new_dc = kwargs['extra'] - super(Exception, self).__init__( - self, - 'The user whose identity is being used to execute queries is ' - 'associated with DC {}.' - .format(self.new_dc) - ) - - -rpc_errors_303_all = { - 'FILE_MIGRATE_(\d+)': FileMigrateError, - 'PHONE_MIGRATE_(\d+)': PhoneMigrateError, - 'NETWORK_MIGRATE_(\d+)': NetworkMigrateError, - 'USER_MIGRATE_(\d+)': UserMigrateError -} diff --git a/telethon/errors/rpc_errors_400.py b/telethon/errors/rpc_errors_400.py deleted file mode 100644 index 63f8dd0d..00000000 --- a/telethon/errors/rpc_errors_400.py +++ /dev/null @@ -1,453 +0,0 @@ -from . import BadRequestError - - -class ApiIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The api_id/api_hash combination is invalid.' - ) - - -class BotMethodInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The API access for bot users is restricted. The method you ' - 'tried to invoke cannot be executed as a bot.' - ) - - -class CdnMethodInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'This method cannot be invoked on a CDN server. Refer to ' - 'https://core.telegram.org/cdn#schema for available methods.' - ) - - -class ChannelInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Invalid channel object. Make sure to pass the right types,' - ' for instance making sure that the request is designed for ' - 'channels or otherwise look for a different one more suited.' - ) - - -class ChannelPrivateError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The channel specified is private and you lack permission to ' - 'access it. Another reason may be that you were banned from it.' - ) - - -class ChatAdminRequiredError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Chat admin privileges are required to do that in the specified ' - 'chat (for example, to send a message in a channel which is not ' - 'yours).' - ) - - -class ChatIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Invalid object ID for a chat. Make sure to pass the right types,' - ' for instance making sure that the request is designed for chats' - ' (not channels/megagroups) or otherwise look for a different one' - ' more suited.\nAn example working with a megagroup and' - ' AddChatUserRequest, it will fail because megagroups are channels' - '. Use InviteToChannelRequest instead.' - ) - - -class ConnectionLangPackInvalid(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The specified language pack is not valid. This is meant to be ' - 'used by official applications only so far, leave it empty.' - ) - - -class ConnectionLayerInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The very first request must always be InvokeWithLayerRequest.' - ) - - -class DcIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'This occurs when an authorization is tried to be exported for ' - 'the same data center one is currently connected to.' - ) - - -class FieldNameEmptyError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The field with the name FIELD_NAME is missing.' - ) - - -class FieldNameInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The field with the name FIELD_NAME is invalid.' - ) - - -class FilePartsInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The number of file parts is invalid.' - ) - - -class FilePartMissingError(BadRequestError): - def __init__(self, **kwargs): - self.which = kwargs['extra'] - super(Exception, self).__init__( - self, - 'Part {} of the file is missing from storage.'.format(self.which) - ) - - -class FilePartInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The file part number is invalid.' - ) - - -class FirstNameInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The first name is invalid.' - ) - - -class InputMethodInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The invoked method does not exist anymore or has never existed.' - ) - - -class InputRequestTooLongError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The input request was too long. This may be a bug in the library ' - 'as it can occur when serializing more bytes than it should (like' - 'appending the vector constructor code at the end of a message).' - ) - - -class LastNameInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The last name is invalid.' - ) - - -class LimitInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'An invalid limit was provided. See ' - 'https://core.telegram.org/api/files#downloading-files' - ) - - -class LocationInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The location given for a file was invalid. See ' - 'https://core.telegram.org/api/files#downloading-files' - ) - - -class Md5ChecksumInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The MD5 check-sums do not match.' - ) - - -class MessageEmptyError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Empty or invalid UTF-8 message was sent.' - ) - - -class MessageIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The specified message ID is invalid.' - ) - - -class MessageTooLongError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Message was too long. Current maximum length is 4096 UTF-8 ' - 'characters.' - ) - - -class MessageNotModifiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Content of the message was not modified.' - ) - - -class MsgWaitFailedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'A waiting call returned an error.' - ) - - -class OffsetInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The given offset was invalid, it must be divisible by 1KB. ' - 'See https://core.telegram.org/api/files#downloading-files' - ) - - - -class PasswordHashInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The password (and thus its hash value) you entered is invalid.' - ) - - -class PeerIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'An invalid Peer was used. Make sure to pass the right peer type.' - ) - - -class PhoneCodeEmptyError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone code is missing.' - ) - - -class PhoneCodeExpiredError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The confirmation code has expired.' - ) - - -class PhoneCodeHashEmptyError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone code hash is missing.' - ) - - -class PhoneCodeInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone code entered was invalid.' - ) - - -class PhoneNumberBannedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The used phone number has been banned from Telegram and cannot ' - 'be used anymore. Maybe check https://www.telegram.org/faq_spam.' - ) - - -class PhoneNumberInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone number is invalid.' - ) - - -class PhoneNumberOccupiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone number is already in use.' - ) - - -class PhoneNumberUnoccupiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The phone number is not yet being used.' - ) - - -class PhotoInvalidDimensionsError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The photo dimensions are invalid.' - ) - - -class TypeConstructorInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The type constructor is invalid.' - ) - - -class UsernameInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Unacceptable username. Must match r"[a-zA-Z][\w\d]{4,31}".' - ) - - -class UsernameNotModifiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The username is not different from the current username.' - ) - - -class UsernameNotOccupiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The username is not in use by anyone else yet.' - ) - - -class UsernameOccupiedError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The username is already taken.' - ) - - -class UsersTooFewError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Not enough users (to create a chat, for example).' - ) - - -class UsersTooMuchError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The maximum number of users has been exceeded (to create a ' - 'chat, for example).' - ) - - -class UserIdInvalidError(BadRequestError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Invalid object ID for an user. Make sure to pass the right types,' - 'for instance making sure that the request is designed for users' - 'or otherwise look for a different one more suited.' - ) - - -rpc_errors_400_all = { - 'API_ID_INVALID': ApiIdInvalidError, - 'BOT_METHOD_INVALID': BotMethodInvalidError, - 'CDN_METHOD_INVALID': CdnMethodInvalidError, - 'CHANNEL_INVALID': ChannelInvalidError, - 'CHANNEL_PRIVATE': ChannelPrivateError, - 'CHAT_ADMIN_REQUIRED': ChatAdminRequiredError, - 'CHAT_ID_INVALID': ChatIdInvalidError, - 'CONNECTION_LAYER_INVALID': ConnectionLayerInvalidError, - 'DC_ID_INVALID': DcIdInvalidError, - 'FIELD_NAME_EMPTY': FieldNameEmptyError, - 'FIELD_NAME_INVALID': FieldNameInvalidError, - 'FILE_PARTS_INVALID': FilePartsInvalidError, - 'FILE_PART_(\d+)_MISSING': FilePartMissingError, - 'FILE_PART_INVALID': FilePartInvalidError, - 'FIRSTNAME_INVALID': FirstNameInvalidError, - 'INPUT_METHOD_INVALID': InputMethodInvalidError, - 'INPUT_REQUEST_TOO_LONG': InputRequestTooLongError, - 'LASTNAME_INVALID': LastNameInvalidError, - 'LIMIT_INVALID': LimitInvalidError, - 'LOCATION_INVALID': LocationInvalidError, - 'MD5_CHECKSUM_INVALID': Md5ChecksumInvalidError, - 'MESSAGE_EMPTY': MessageEmptyError, - 'MESSAGE_ID_INVALID': MessageIdInvalidError, - 'MESSAGE_TOO_LONG': MessageTooLongError, - 'MESSAGE_NOT_MODIFIED': MessageNotModifiedError, - 'MSG_WAIT_FAILED': MsgWaitFailedError, - 'OFFSET_INVALID': OffsetInvalidError, - 'PASSWORD_HASH_INVALID': PasswordHashInvalidError, - 'PEER_ID_INVALID': PeerIdInvalidError, - 'PHONE_CODE_EMPTY': PhoneCodeEmptyError, - 'PHONE_CODE_EXPIRED': PhoneCodeExpiredError, - 'PHONE_CODE_HASH_EMPTY': PhoneCodeHashEmptyError, - 'PHONE_CODE_INVALID': PhoneCodeInvalidError, - 'PHONE_NUMBER_BANNED': PhoneNumberBannedError, - 'PHONE_NUMBER_INVALID': PhoneNumberInvalidError, - 'PHONE_NUMBER_OCCUPIED': PhoneNumberOccupiedError, - 'PHONE_NUMBER_UNOCCUPIED': PhoneNumberUnoccupiedError, - 'PHOTO_INVALID_DIMENSIONS': PhotoInvalidDimensionsError, - 'TYPE_CONSTRUCTOR_INVALID': TypeConstructorInvalidError, - 'USERNAME_INVALID': UsernameInvalidError, - 'USERNAME_NOT_MODIFIED': UsernameNotModifiedError, - 'USERNAME_NOT_OCCUPIED': UsernameNotOccupiedError, - 'USERNAME_OCCUPIED': UsernameOccupiedError, - 'USERS_TOO_FEW': UsersTooFewError, - 'USERS_TOO_MUCH': UsersTooMuchError, - 'USER_ID_INVALID': UserIdInvalidError, -} diff --git a/telethon/errors/rpc_errors_401.py b/telethon/errors/rpc_errors_401.py deleted file mode 100644 index 5b22cb73..00000000 --- a/telethon/errors/rpc_errors_401.py +++ /dev/null @@ -1,98 +0,0 @@ -from . import UnauthorizedError - - -class ActiveUserRequiredError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The method is only available to already activated users.' - ) - - -class AuthKeyInvalidError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The key is invalid.' - ) - - -class AuthKeyPermEmptyError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The method is unavailable for temporary authorization key, not ' - 'bound to permanent.' - ) - - -class AuthKeyUnregisteredError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The key is not registered in the system.' - ) - - -class InviteHashExpiredError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The chat the user tried to join has expired and is not valid ' - 'anymore.' - ) - - -class SessionExpiredError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The authorization has expired.' - ) - - -class SessionPasswordNeededError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'Two-steps verification is enabled and a password is required.' - ) - - -class SessionRevokedError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The authorization has been invalidated, because of the user ' - 'terminating all sessions.' - ) - - -class UserAlreadyParticipantError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The authenticated user is already a participant of the chat.' - ) - - -class UserDeactivatedError(UnauthorizedError): - def __init__(self, **kwargs): - super(Exception, self).__init__( - self, - 'The user has been deleted/deactivated.' - ) - - -rpc_errors_401_all = { - 'ACTIVE_USER_REQUIRED': ActiveUserRequiredError, - 'AUTH_KEY_INVALID': AuthKeyInvalidError, - 'AUTH_KEY_PERM_EMPTY': AuthKeyPermEmptyError, - 'AUTH_KEY_UNREGISTERED': AuthKeyUnregisteredError, - 'INVITE_HASH_EXPIRED': InviteHashExpiredError, - 'SESSION_EXPIRED': SessionExpiredError, - 'SESSION_PASSWORD_NEEDED': SessionPasswordNeededError, - 'SESSION_REVOKED': SessionRevokedError, - 'USER_ALREADY_PARTICIPANT': UserAlreadyParticipantError, - 'USER_DEACTIVATED': UserDeactivatedError, -} diff --git a/telethon/errors/rpc_errors_420.py b/telethon/errors/rpc_errors_420.py deleted file mode 100644 index 8106cc5c..00000000 --- a/telethon/errors/rpc_errors_420.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import FloodError - - -class FloodWaitError(FloodError): - def __init__(self, **kwargs): - self.seconds = kwargs['extra'] - super(Exception, self).__init__( - self, - 'A wait of {} seconds is required.' - .format(self.seconds) - ) - - -rpc_errors_420_all = { - 'FLOOD_WAIT_(\d+)': FloodWaitError -} diff --git a/telethon_generator/error_descriptions b/telethon_generator/error_descriptions new file mode 100644 index 00000000..f0a14e68 --- /dev/null +++ b/telethon_generator/error_descriptions @@ -0,0 +1,65 @@ +# These are comments. Spaces around the = are optional. Empty lines ignored. +#CODE=Human readable description + +FILE_MIGRATE_X=The file to be accessed is currently stored in DC {} +PHONE_MIGRATE_X=The phone number a user is trying to use for authorization is associated with DC {} +NETWORK_MIGRATE_X=The source IP address is associated with DC {} +USER_MIGRATE_X=The user whose identity is being used to execute queries is associated with DC {} +API_ID_INVALID=The api_id/api_hash combination is invalid +BOT_METHOD_INVALID=The API access for bot users is restricted. The method you tried to invoke cannot be executed as a bot +CDN_METHOD_INVALID=This method cannot be invoked on a CDN server. Refer to https://core.telegram.org/cdn#schema for available methods +CHANNEL_INVALID=Invalid channel object. Make sure to pass the right types, for instance making sure that the request is designed for channels or otherwise look for a different one more suited +CHANNEL_PRIVATE=The channel specified is private and you lack permission to access it. Another reason may be that you were banned from it +CHAT_ADMIN_REQUIRED=Chat admin privileges are required to do that in the specified chat (for example, to send a message in a channel which is not yours) +CHAT_ID_INVALID=Invalid object ID for a chat. Make sure to pass the right types, for instance making sure that the request is designed for chats (not channels/megagroups) or otherwise look for a different one more suited\nAn example working with a megagroup and AddChatUserRequest, it will fail because megagroups are channels. Use InviteToChannelRequest instead +CONNECTION_LANG_PACK_INVALID=The specified language pack is not valid. This is meant to be used by official applications only so far, leave it empty +CONNECTION_LAYER_INVALID=The very first request must always be InvokeWithLayerRequest +DC_ID_INVALID=This occurs when an authorization is tried to be exported for the same data center one is currently connected to +FIELD_NAME_EMPTY=The field with the name FIELD_NAME is missing +FIELD_NAME_INVALID=The field with the name FIELD_NAME is invalid +FILE_PARTS_INVALID=The number of file parts is invalid +FILE_PART_X_MISSING=Part {} of the file is missing from storage +FILE_PART_INVALID=The file part number is invalid +FIRSTNAME_INVALID=The first name is invalid +INPUT_METHOD_INVALID=The invoked method does not exist anymore or has never existed +INPUT_REQUEST_TOO_LONG=The input request was too long. This may be a bug in the library as it can occur when serializing more bytes than it should (likeappending the vector constructor code at the end of a message) +LASTNAME_INVALID=The last name is invalid +LIMIT_INVALID=An invalid limit was provided. See https://core.telegram.org/api/files#downloading-files +LOCATION_INVALID=The location given for a file was invalid. See https://core.telegram.org/api/files#downloading-files +MD5_CHECKSUM_INVALID=The MD5 check-sums do not match +MESSAGE_EMPTY=Empty or invalid UTF-8 message was sent +MESSAGE_ID_INVALID=The specified message ID is invalid +MESSAGE_TOO_LONG=Message was too long. Current maximum length is 4096 UTF-8 characters +MESSAGE_NOT_MODIFIED=Content of the message was not modified +MSG_WAIT_FAILED=A waiting call returned an error +OFFSET_INVALID=The given offset was invalid, it must be divisible by 1KB. See https://core.telegram.org/api/files#downloading-files +PASSWORD_HASH_INVALID=The password (and thus its hash value) you entered is invalid +PEER_ID_INVALID=An invalid Peer was used. Make sure to pass the right peer type +PHONE_CODE_EMPTY=The phone code is missing +PHONE_CODE_EXPIRED=The confirmation code has expired +PHONE_CODE_HASH_EMPTY=The phone code hash is missing +PHONE_CODE_INVALID=The phone code entered was invalid +PHONE_NUMBER_BANNED=The used phone number has been banned from Telegram and cannot be used anymore. Maybe check https://www.telegram.org/faq_spam +PHONE_NUMBER_INVALID=The phone number is invalid +PHONE_NUMBER_OCCUPIED=The phone number is already in use +PHONE_NUMBER_UNOCCUPIED=The phone number is not yet being used +PHOTO_INVALID_DIMENSIONS=The photo dimensions are invalid +TYPE_CONSTRUCTOR_INVALID=The type constructor is invalid +USERNAME_INVALID=Unacceptable username. Must match r"[a-zA-Z][\w\d]{4,31}" +USERNAME_NOT_MODIFIED=The username is not different from the current username +USERNAME_NOT_OCCUPIED=The username is not in use by anyone else yet +USERNAME_OCCUPIED=The username is already taken +USERS_TOO_FEW=Not enough users (to create a chat, for example) +USERS_TOO_MUCH=The maximum number of users has been exceeded (to create a chat, for example) +USER_ID_INVALID=Invalid object ID for an user. Make sure to pass the right types, for instance making sure that the request is designed for users or otherwise look for a different one more suited +ACTIVE_USER_REQUIRED=The method is only available to already activated users +AUTH_KEY_INVALID=The key is invalid +AUTH_KEY_PERM_EMPTY=The method is unavailable for temporary authorization key, not bound to permanent +AUTH_KEY_UNREGISTERED=The key is not registered in the system +INVITE_HASH_EXPIRED=The chat the user tried to join has expired and is not valid anymore +SESSION_EXPIRED=The authorization has expired +SESSION_PASSWORD_NEEDED=Two-steps verification is enabled and a password is required +SESSION_REVOKED=The authorization has been invalidated, because of the user terminating all sessions +USER_ALREADY_PARTICIPANT=The authenticated user is already a participant of the chat +USER_DEACTIVATED=The user has been deleted/deactivated +FLOOD_WAIT_X=A wait of {} seconds is required diff --git a/telethon_generator/error_generator.py b/telethon_generator/error_generator.py new file mode 100644 index 00000000..fb5d0ef7 --- /dev/null +++ b/telethon_generator/error_generator.py @@ -0,0 +1,172 @@ +import json +import re +import urllib.request +from collections import defaultdict + +URL = 'https://rpc.pwrtelegram.xyz/?all' +OUTPUT = '../telethon/errors/rpc_error_list.py' +JSON_OUTPUT = 'errors.json' + +known_base_classes = { + 303: 'InvalidDCError', + 400: 'BadRequestError', + 401: 'UnauthorizedError', + 403: 'ForbiddenError', + 404: 'NotFoundError', + 420: 'FloodError', + 500: 'ServerError', +} + +# The API doesn't return the code for some (vital) errors. They are +# all assumed to be 400, except these well-known ones that aren't. +known_codes = { + 'ACTIVE_USER_REQUIRED': 401, + 'AUTH_KEY_UNREGISTERED': 401, + 'USER_DEACTIVATED': 401 +} + + +def fetch_errors(url=URL, output=JSON_OUTPUT): + print('Opening a connection to', url, '...') + r = urllib.request.urlopen(url) + print('Checking response...') + data = json.loads( + r.read().decode(r.info().get_param('charset') or 'utf-8') + ) + if data.get('ok'): + print('Response was okay, saving data') + with open(output, 'w', encoding='utf-8') as f: + json.dump(data, f) + return True + else: + print('The data received was not okay:') + print(json.dumps(data, indent=4)) + return False + + +def get_class_name(error_code): + if isinstance(error_code, int): + return known_base_classes.get( + error_code, 'RPCError' + str(error_code).replace('-', 'Neg') + ) + + if 'FIRSTNAME' in error_code: + error_code = error_code.replace('FIRSTNAME', 'FIRST_NAME') + + result = re.sub( + r'_([a-z])', lambda m: m.group(1).upper(), error_code.lower() + ) + return result[:1].upper() + result[1:].replace('_', '') + 'Error' + + +def write_error(f, code, name, desc, capture_name): + f.write( + f'\n' + f'\n' + f'class {name}({get_class_name(code)}):\n' + f' def __init__(self, **kwargs):\n' + f' ' + ) + if capture_name: + f.write( + f"self.{capture_name} = int(kwargs.get('capture', 0))\n" + f" " + ) + f.write(f'super(Exception, self).__init__(self, {repr(desc)}') + if capture_name: + f.write(f'.format(self.{capture_name})') + f.write(')\n') + + +def generate_code(json_file=JSON_OUTPUT, output=OUTPUT): + with open(json_file, encoding='utf-8') as f: + data = json.load(f) + + errors = defaultdict(set) + # PWRTelegram's API doesn't return all errors, which we do need here. + # Add some special known-cases manually first. + errors[420].add('FLOOD_WAIT_X') + errors[401].update(( + 'AUTH_KEY_INVALID', 'SESSION_EXPIRED', 'SESSION_REVOKED' + )) + errors[303].update(( + 'FILE_MIGRATE_X', 'PHONE_MIGRATE_X', + 'NETWORK_MIGRATE_X', 'USER_MIGRATE_X' + )) + for error_code, method_errors in data['result'].items(): + for error_list in method_errors.values(): + for error in error_list: + errors[int(error_code)].add(re.sub('_\d+', '_X', error).upper()) + + # Some errors are in the human result, but not with a code. Assume code 400 + for error in data['human_result']: + if error[0] != '-' and not error.isdigit(): + error = re.sub('_\d+', '_X', error).upper() + if not any(error in es for es in errors.values()): + errors[known_codes.get(error, 400)].add(error) + + # Some error codes are not known, so create custom base classes if needed + needed_base_classes = [ + (e, get_class_name(e)) for e in errors if e not in known_base_classes + ] + + # Prefer the descriptions that are related with Telethon way of coding to + # those that PWRTelegram's API provides. + telethon_descriptions = {} + with open('error_descriptions', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#'): + equal = line.index('=') + message, description = line[:equal], line[equal + 1:] + telethon_descriptions[message.rstrip()] = description.lstrip() + + # Names for the captures, or 'x' if unknown + capture_names = { + 'FloodWaitError': 'seconds', + 'FileMigrateError': 'new_dc', + 'NetworkMigrateError': 'new_dc', + 'PhoneMigrateError': 'new_dc', + 'UserMigrateError': 'new_dc', + 'FilePartMissingError': 'which' + } + + # Everything ready, generate the code + with open(output, 'w', encoding='utf-8') as f: + f.write( + f'from .rpc_base_errors import RPCError, BadMessageError, ' + f'{", ".join(known_base_classes.values())}\n' + ) + for code, cls in needed_base_classes: + f.write( + f'\n' + f'\n' + f'class {cls}(RPCError):\n' + f' code = {code}\n' + ) + + patterns = [] # Save this dictionary later in the generated code + for error_code, error_set in errors.items(): + for error in sorted(error_set): + description = telethon_descriptions.get( + error, '\n'.join(data['human_result'].get( + error, ['No description known.'] + )) + ) + has_captures = '_X' in error + if has_captures: + name = get_class_name(error.replace('_X', '')) + pattern = error.replace('_X', r'_(\d+)') + else: + name, pattern = get_class_name(error), error + + patterns.append((pattern, name)) + capture = capture_names.get(name, 'x') if has_captures else None + # TODO Some errors have the same name but different code, + # split this accross different files? + write_error(f, error_code, name, description, capture) + + f.write('\n\nrpc_errors_all = {\n') + for pattern, name in patterns: + f.write(f' {repr(pattern)}: {name},\n') + f.write('}\n') diff --git a/telethon_generator/errors.json b/telethon_generator/errors.json new file mode 100644 index 00000000..e807ff2d --- /dev/null +++ b/telethon_generator/errors.json @@ -0,0 +1 @@ +{"ok": true, "result": {"400": {"account.updateProfile": ["ABOUT_TOO_LONG", "FIRSTNAME_INVALID"], "auth.importBotAuthorization": ["ACCESS_TOKEN_EXPIRED", "ACCESS_TOKEN_INVALID", "API_ID_INVALID"], "auth.sendCode": ["API_ID_INVALID", "INPUT_REQUEST_TOO_LONG", "PHONE_NUMBER_APP_SIGNUP_FORBIDDEN", "PHONE_NUMBER_BANNED", "PHONE_NUMBER_FLOOD", "PHONE_NUMBER_INVALID", "PHONE_PASSWORD_PROTECTED"], "messages.setInlineBotResults": ["ARTICLE_TITLE_EMPTY", "BUTTON_DATA_INVALID", "BUTTON_TYPE_INVALID", "BUTTON_URL_INVALID", "MESSAGE_EMPTY", "QUERY_ID_INVALID", "REPLY_MARKUP_INVALID", "RESULT_TYPE_INVALID", "SEND_MESSAGE_TYPE_INVALID", "START_PARAM_INVALID"], "auth.importAuthorization": ["AUTH_BYTES_INVALID", "USER_ID_INVALID"], "invokeWithLayer": ["AUTH_BYTES_INVALID", "CDN_METHOD_INVALID", "CONNECTION_API_ID_INVALID", "CONNECTION_LANG_PACK_INVALID", "INPUT_LAYER_INVALID"], "channels.inviteToChannel": ["BOT_GROUPS_BLOCKED", "BOTS_TOO_MUCH", "CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ADMIN_REQUIRED", "INPUT_USER_DEACTIVATED", "USER_BANNED_IN_CHANNEL", "USER_BLOCKED", "USER_BOT", "USER_ID_INVALID", "USER_KICKED", "USER_NOT_MUTUAL_CONTACT", "USERS_TOO_MUCH"], "messages.getInlineBotResults": ["BOT_INLINE_DISABLED", "BOT_INVALID"], "messages.startBot": ["BOT_INVALID", "PEER_ID_INVALID", "START_PARAM_EMPTY", "START_PARAM_INVALID"], "messages.uploadMedia": ["BOT_MISSING", "PEER_ID_INVALID"], "stickers.addStickerToSet": ["BOT_MISSING", "STICKERSET_INVALID"], "stickers.changeStickerPosition": ["BOT_MISSING", "STICKER_INVALID"], "stickers.createStickerSet": ["BOT_MISSING", "PACK_SHORT_NAME_INVALID", "PEER_ID_INVALID", "STICKERS_EMPTY", "USER_ID_INVALID"], "stickers.removeStickerFromSet": ["BOT_MISSING", "STICKER_INVALID"], "messages.sendMessage": ["BUTTON_DATA_INVALID", "BUTTON_TYPE_INVALID", "BUTTON_URL_INVALID", "CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ADMIN_REQUIRED", "CHAT_ID_INVALID", "ENTITY_MENTION_USER_INVALID", "INPUT_USER_DEACTIVATED", "MESSAGE_EMPTY", "MESSAGE_TOO_LONG", "PEER_ID_INVALID", "REPLY_MARKUP_INVALID", "USER_BANNED_IN_CHANNEL", "USER_IS_BLOCKED", "USER_IS_BOT", "YOU_BLOCKED_USER"], "phone.acceptCall": ["CALL_ALREADY_ACCEPTED", "CALL_ALREADY_DECLINED", "CALL_PEER_INVALID", "CALL_PROTOCOL_FLAGS_INVALID"], "phone.discardCall": ["CALL_ALREADY_ACCEPTED", "CALL_PEER_INVALID"], "phone.confirmCall": ["CALL_ALREADY_DECLINED", "CALL_PEER_INVALID"], "phone.receivedCall": ["CALL_ALREADY_DECLINED", "CALL_PEER_INVALID"], "phone.saveCallDebug": ["CALL_PEER_INVALID", "DATA_JSON_INVALID"], "phone.setCallRating": ["CALL_PEER_INVALID"], "phone.requestCall": ["CALL_PROTOCOL_FLAGS_INVALID", "PARTICIPANT_VERSION_OUTDATED", "USER_ID_INVALID"], "updates.getDifference": ["CDN_METHOD_INVALID", "DATE_EMPTY", "PERSISTENT_TIMESTAMP_EMPTY", "PERSISTENT_TIMESTAMP_INVALID"], "upload.getCdnFileHashes": ["CDN_METHOD_INVALID", "RSA_DECRYPT_FAILED"], "channels.checkUsername": ["CHANNEL_INVALID", "CHAT_ID_INVALID", "USERNAME_INVALID"], "channels.deleteChannel": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.deleteMessages": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.deleteUserHistory": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED"], "channels.editAbout": ["CHANNEL_INVALID", "CHAT_ABOUT_NOT_MODIFIED", "CHAT_ABOUT_TOO_LONG", "CHAT_ADMIN_REQUIRED"], "channels.editAdmin": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "USER_CREATOR", "USER_ID_INVALID", "USER_NOT_MUTUAL_CONTACT"], "channels.editBanned": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ADMIN_REQUIRED", "USER_ADMIN_INVALID", "USER_ID_INVALID"], "channels.editPhoto": ["CHANNEL_INVALID"], "channels.editTitle": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "CHAT_NOT_MODIFIED"], "channels.exportInvite": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "INVITE_HASH_EXPIRED"], "channels.exportMessageLink": ["CHANNEL_INVALID"], "channels.getAdminLog": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED"], "channels.getChannels": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.getFullChannel": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.getMessages": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "MESSAGE_IDS_EMPTY"], "channels.getParticipant": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "USER_ID_INVALID", "USER_NOT_PARTICIPANT"], "channels.getParticipants": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ADMIN_REQUIRED", "INPUT_CONSTRUCTOR_INVALID"], "channels.joinChannel": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHANNELS_TOO_MUCH"], "channels.leaveChannel": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "USER_CREATOR", "USER_NOT_PARTICIPANT"], "channels.readHistory": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.readMessageContents": ["CHANNEL_INVALID", "CHANNEL_PRIVATE"], "channels.reportSpam": ["CHANNEL_INVALID"], "channels.setStickers": ["CHANNEL_INVALID", "PARTICIPANTS_TOO_FEW"], "channels.toggleInvites": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED"], "channels.toggleSignatures": ["CHANNEL_INVALID"], "channels.updatePinnedMessage": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "CHAT_NOT_MODIFIED"], "channels.updateUsername": ["CHANNEL_INVALID", "CHAT_ADMIN_REQUIRED", "USERNAME_INVALID", "USERNAME_OCCUPIED"], "messages.editMessage": ["CHANNEL_INVALID", "MESSAGE_EDIT_TIME_EXPIRED", "MESSAGE_EMPTY", "MESSAGE_ID_INVALID", "MESSAGE_NOT_MODIFIED", "PEER_ID_INVALID"], "messages.forwardMessages": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ADMIN_REQUIRED", "CHAT_ID_INVALID", "MEDIA_EMPTY", "MESSAGE_ID_INVALID", "MESSAGE_IDS_EMPTY", "PEER_ID_INVALID", "RANDOM_ID_INVALID", "USER_BANNED_IN_CHANNEL", "USER_IS_BLOCKED", "USER_IS_BOT", "YOU_BLOCKED_USER"], "messages.getHistory": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.getPeerSettings": ["CHANNEL_INVALID", "PEER_ID_INVALID"], "messages.sendMedia": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "FILE_PART_0_MISSING", "FILE_PART_LENGTH_INVALID", "FILE_PARTS_INVALID", "INPUT_USER_DEACTIVATED", "MEDIA_CAPTION_TOO_LONG", "MEDIA_EMPTY", "PEER_ID_INVALID", "PHOTO_EXT_INVALID", "USER_IS_BLOCKED", "USER_IS_BOT", "WEBPAGE_CURL_FAILED", "WEBPAGE_MEDIA_EMPTY"], "messages.setTyping": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "CHAT_ID_INVALID", "PEER_ID_INVALID"], "updates.getChannelDifference": ["CHANNEL_INVALID", "CHANNEL_PRIVATE", "PERSISTENT_TIMESTAMP_EMPTY", "PERSISTENT_TIMESTAMP_INVALID", "RANGES_INVALID"], "messages.getMessagesViews": ["CHANNEL_PRIVATE", "CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.getPeerDialogs": ["CHANNEL_PRIVATE", "PEER_ID_INVALID"], "messages.addChatUser": ["CHAT_ADMIN_REQUIRED", "CHAT_ID_INVALID", "PEER_ID_INVALID", "USER_ALREADY_PARTICIPANT", "USER_ID_INVALID", "USERS_TOO_MUCH"], "messages.discardEncryption": ["CHAT_ID_EMPTY", "ENCRYPTION_ALREADY_DECLINED", "ENCRYPTION_ID_INVALID"], "messages.acceptEncryption": ["CHAT_ID_INVALID", "ENCRYPTION_ALREADY_ACCEPTED", "ENCRYPTION_ALREADY_DECLINED"], "messages.deleteChatUser": ["CHAT_ID_INVALID", "PEER_ID_INVALID", "USER_NOT_PARTICIPANT"], "messages.editChatAdmin": ["CHAT_ID_INVALID"], "messages.editChatPhoto": ["CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.editChatTitle": ["CHAT_ID_INVALID"], "messages.exportChatInvite": ["CHAT_ID_INVALID"], "messages.forwardMessage": ["CHAT_ID_INVALID", "MESSAGE_ID_INVALID", "PEER_ID_INVALID", "YOU_BLOCKED_USER"], "messages.getChats": ["CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.getFullChat": ["CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.migrateChat": ["CHAT_ID_INVALID", "PEER_ID_INVALID"], "messages.reportEncryptedSpam": ["CHAT_ID_INVALID"], "messages.sendEncrypted": ["CHAT_ID_INVALID", "DATA_INVALID", "MSG_WAIT_FAILED"], "messages.setEncryptedTyping": ["CHAT_ID_INVALID"], "messages.toggleChatAdmins": ["CHAT_ID_INVALID", "CHAT_NOT_MODIFIED"], "channels.createChannel": ["CHAT_TITLE_EMPTY"], "auth.recoverPassword": ["CODE_EMPTY"], "account.confirmPhone": ["CODE_HASH_INVALID", "PHONE_CODE_EMPTY"], "initConnection": ["CONNECTION_LAYER_INVALID", "INPUT_FETCH_FAIL"], "contacts.block": ["CONTACT_ID_INVALID"], "contacts.deleteContact": ["CONTACT_ID_INVALID"], "contacts.unblock": ["CONTACT_ID_INVALID"], "messages.getBotCallbackAnswer": ["DATA_INVALID", "MESSAGE_ID_INVALID", "PEER_ID_INVALID"], "messages.sendEncryptedService": ["DATA_INVALID", "MSG_WAIT_FAILED"], "auth.exportAuthorization": ["DC_ID_INVALID"], "messages.requestEncryption": ["DH_G_A_INVALID", "USER_ID_INVALID"], "auth.bindTempAuthKey": ["ENCRYPTED_MESSAGE_INVALID", "INPUT_REQUEST_TOO_LONG", "TEMP_AUTH_KEY_EMPTY"], "messages.setBotPrecheckoutResults": ["ERROR_TEXT_EMPTY"], "contacts.importCard": ["EXPORT_CARD_INVALID"], "upload.getFile": ["FILE_ID_INVALID", "LIMIT_INVALID", "LOCATION_INVALID", "OFFSET_INVALID"], "photos.uploadProfilePhoto": ["FILE_PART_0_MISSING", "FILE_PARTS_INVALID", "IMAGE_PROCESS_FAILED", "PHOTO_CROP_SIZE_SMALL", "PHOTO_EXT_INVALID"], "upload.saveBigFilePart": ["FILE_PART_EMPTY", "FILE_PART_INVALID", "FILE_PART_SIZE_INVALID", "FILE_PARTS_INVALID"], "upload.saveFilePart": ["FILE_PART_EMPTY", "FILE_PART_INVALID"], "auth.signUp": ["FIRSTNAME_INVALID", "PHONE_CODE_EMPTY", "PHONE_CODE_EXPIRED", "PHONE_CODE_INVALID", "PHONE_NUMBER_FLOOD", "PHONE_NUMBER_INVALID", "PHONE_NUMBER_OCCUPIED"], "messages.saveGif": ["GIF_ID_INVALID"], "account.resetAuthorization": ["HASH_INVALID"], "account.sendConfirmPhoneCode": ["HASH_INVALID"], "messages.sendInlineBotResult": ["INLINE_RESULT_EXPIRED", "PEER_ID_INVALID", "QUERY_ID_EMPTY"], "messages.getDialogs": ["INPUT_CONSTRUCTOR_INVALID", "OFFSET_PEER_ID_INVALID"], "messages.search": ["INPUT_CONSTRUCTOR_INVALID", "INPUT_USER_DEACTIVATED", "PEER_ID_INVALID", "PEER_ID_NOT_SUPPORTED", "SEARCH_QUERY_EMPTY", "USER_ID_INVALID"], "messages.checkChatInvite": ["INVITE_HASH_EMPTY", "INVITE_HASH_EXPIRED", "INVITE_HASH_INVALID"], "messages.importChatInvite": ["INVITE_HASH_EMPTY", "INVITE_HASH_EXPIRED", "INVITE_HASH_INVALID", "USER_ALREADY_PARTICIPANT", "USERS_TOO_MUCH"], "{}": ["INVITE_HASH_EXPIRED"], "langpack.getDifference": ["LANG_PACK_INVALID"], "langpack.getLangPack": ["LANG_PACK_INVALID"], "langpack.getLanguages": ["LANG_PACK_INVALID"], "langpack.getStrings": ["LANG_PACK_INVALID"], "upload.getWebFile": ["LOCATION_INVALID"], "photos.getUserPhotos": ["MAX_ID_INVALID", "USER_ID_INVALID"], "auth.sendInvites": ["MESSAGE_EMPTY"], "messages.editInlineBotMessage": ["MESSAGE_ID_INVALID", "MESSAGE_NOT_MODIFIED"], "messages.getInlineGameHighScores": ["MESSAGE_ID_INVALID", "USER_BOT_REQUIRED"], "messages.setInlineGameScore": ["MESSAGE_ID_INVALID", "USER_BOT_REQUIRED"], "payments.getPaymentForm": ["MESSAGE_ID_INVALID"], "payments.getPaymentReceipt": ["MESSAGE_ID_INVALID"], "payments.sendPaymentForm": ["MESSAGE_ID_INVALID"], "payments.validateRequestedInfo": ["MESSAGE_ID_INVALID"], "messages.readEncryptedHistory": ["MSG_WAIT_FAILED"], "messages.receivedQueue": ["MSG_WAIT_FAILED"], "messages.sendEncryptedFile": ["MSG_WAIT_FAILED"], "account.updatePasswordSettings": ["NEW_SALT_INVALID", "NEW_SETTINGS_INVALID", "PASSWORD_HASH_INVALID"], "auth.requestPasswordRecovery": ["PASSWORD_EMPTY"], "account.getPasswordSettings": ["PASSWORD_HASH_INVALID"], "account.getTmpPassword": ["PASSWORD_HASH_INVALID", "TMP_PASSWORD_DISABLED"], "auth.checkPassword": ["PASSWORD_HASH_INVALID"], "account.getNotifySettings": ["PEER_ID_INVALID"], "account.reportPeer": ["PEER_ID_INVALID"], "account.updateNotifySettings": ["PEER_ID_INVALID"], "contacts.resetTopPeerRating": ["PEER_ID_INVALID"], "messages.deleteHistory": ["PEER_ID_INVALID"], "messages.getGameHighScores": ["PEER_ID_INVALID", "USER_BOT_REQUIRED"], "messages.getMessageEditData": ["PEER_ID_INVALID"], "messages.getUnreadMentions": ["PEER_ID_INVALID"], "messages.hideReportSpam": ["PEER_ID_INVALID"], "messages.readHistory": ["PEER_ID_INVALID"], "messages.reorderPinnedDialogs": ["PEER_ID_INVALID"], "messages.reportSpam": ["PEER_ID_INVALID"], "messages.saveDraft": ["PEER_ID_INVALID"], "messages.sendScreenshotNotification": ["PEER_ID_INVALID"], "messages.setGameScore": ["PEER_ID_INVALID", "USER_BOT_REQUIRED"], "messages.toggleDialogPin": ["PEER_ID_INVALID"], "auth.signIn": ["PHONE_CODE_EMPTY", "PHONE_CODE_EXPIRED", "PHONE_CODE_INVALID", "PHONE_NUMBER_INVALID", "PHONE_NUMBER_UNOCCUPIED"], "auth.checkPhone": ["PHONE_NUMBER_BANNED", "PHONE_NUMBER_INVALID"], "account.changePhone": ["PHONE_NUMBER_INVALID"], "account.sendChangePhoneCode": ["PHONE_NUMBER_INVALID"], "auth.cancelCode": ["PHONE_NUMBER_INVALID"], "auth.resendCode": ["PHONE_NUMBER_INVALID"], "account.getPrivacy": ["PRIVACY_KEY_INVALID"], "account.setPrivacy": ["PRIVACY_KEY_INVALID"], "bots.answerWebhookJSONQuery": ["QUERY_ID_INVALID", "USER_BOT_INVALID"], "messages.setBotCallbackAnswer": ["QUERY_ID_INVALID"], "messages.setBotShippingResults": ["QUERY_ID_INVALID"], "contacts.search": ["QUERY_TOO_SHORT", "SEARCH_QUERY_EMPTY"], "messages.getDhConfig": ["RANDOM_LENGTH_INVALID"], "upload.reuploadCdnFile": ["RSA_DECRYPT_FAILED"], "messages.searchGifs": ["SEARCH_QUERY_EMPTY"], "messages.searchGlobal": ["SEARCH_QUERY_EMPTY"], "messages.getDocumentByHash": ["SHA256_HASH_INVALID"], "messages.faveSticker": ["STICKER_ID_INVALID"], "messages.saveRecentSticker": ["STICKER_ID_INVALID"], "messages.getStickerSet": ["STICKERSET_INVALID"], "messages.installStickerSet": ["STICKERSET_INVALID"], "messages.uninstallStickerSet": ["STICKERSET_INVALID"], "account.registerDevice": ["TOKEN_INVALID"], "account.unregisterDevice": ["TOKEN_INVALID"], "account.setAccountTTL": ["TTL_DAYS_INVALID"], "contacts.getTopPeers": ["TYPES_EMPTY"], "bots.sendCustomRequest": ["USER_BOT_INVALID"], "messages.getCommonChats": ["USER_ID_INVALID"], "users.getFullUser": ["USER_ID_INVALID"], "account.checkUsername": ["USERNAME_INVALID"], "account.updateUsername": ["USERNAME_INVALID", "USERNAME_NOT_MODIFIED", "USERNAME_OCCUPIED"], "contacts.resolveUsername": ["USERNAME_INVALID", "USERNAME_NOT_OCCUPIED"], "messages.createChat": ["USERS_TOO_FEW"]}, "401": {"contacts.resolveUsername": ["AUTH_KEY_PERM_EMPTY", "SESSION_PASSWORD_NEEDED"], "messages.getHistory": ["AUTH_KEY_PERM_EMPTY"], "auth.signIn": ["SESSION_PASSWORD_NEEDED"], "messages.getDialogs": ["SESSION_PASSWORD_NEEDED"], "updates.getDifference": ["SESSION_PASSWORD_NEEDED"], "updates.getState": ["SESSION_PASSWORD_NEEDED"], "upload.saveFilePart": ["SESSION_PASSWORD_NEEDED"], "users.getUsers": ["SESSION_PASSWORD_NEEDED"]}, "500": {"auth.sendCode": ["AUTH_RESTART"], "phone.acceptCall": ["CALL_OCCUPY_FAILED"], "messages.acceptEncryption": ["ENCRYPTION_OCCUPY_FAILED"], "updates.getChannelDifference": ["HISTORY_GET_FAILED", "PERSISTENT_TIMESTAMP_OUTDATED"], "users.getUsers": ["MEMBER_NO_LOCATION", "NEED_MEMBER_INVALID"], "auth.signUp": ["MEMBER_OCCUPY_PRIMARY_LOC_FAILED", "REG_ID_GENERATE_FAILED"], "channels.getChannels": ["NEED_CHAT_INVALID"], "messages.editChatTitle": ["NEED_CHAT_INVALID"], "contacts.deleteContacts": ["NEED_MEMBER_INVALID"], "contacts.importCard": ["NEED_MEMBER_INVALID"], "updates.getDifference": ["NEED_MEMBER_INVALID"], "phone.requestCall": ["PARTICIPANT_CALL_FAILED"], "messages.forwardMessages": ["PTS_CHANGE_EMPTY", "RANDOM_ID_DUPLICATE"], "messages.sendMessage": ["RANDOM_ID_DUPLICATE"], "messages.sendMedia": ["STORAGE_CHECK_FAILED"], "upload.getCdnFile": ["UNKNOWN_METHOD"]}, "403": {"channels.getFullChannel": ["CHANNEL_PUBLIC_GROUP_NA"], "updates.getChannelDifference": ["CHANNEL_PUBLIC_GROUP_NA"], "channels.editAdmin": ["CHAT_ADMIN_INVITE_REQUIRED", "RIGHT_FORBIDDEN", "USER_PRIVACY_RESTRICTED"], "messages.migrateChat": ["CHAT_ADMIN_REQUIRED"], "messages.forwardMessages": ["CHAT_SEND_GIFS_FORBIDDEN", "CHAT_SEND_MEDIA_FORBIDDEN", "CHAT_SEND_STICKERS_FORBIDDEN", "CHAT_WRITE_FORBIDDEN"], "channels.inviteToChannel": ["CHAT_WRITE_FORBIDDEN", "USER_CHANNELS_TOO_MUCH", "USER_PRIVACY_RESTRICTED"], "messages.editMessage": ["CHAT_WRITE_FORBIDDEN", "MESSAGE_AUTHOR_REQUIRED"], "messages.sendMedia": ["CHAT_WRITE_FORBIDDEN"], "messages.sendMessage": ["CHAT_WRITE_FORBIDDEN"], "messages.setTyping": ["CHAT_WRITE_FORBIDDEN"], "messages.getMessageEditData": ["MESSAGE_AUTHOR_REQUIRED"], "channels.deleteMessages": ["MESSAGE_DELETE_FORBIDDEN"], "messages.deleteMessages": ["MESSAGE_DELETE_FORBIDDEN"], "messages.setInlineBotResults": ["USER_BOT_INVALID"], "phone.requestCall": ["USER_IS_BLOCKED", "USER_PRIVACY_RESTRICTED"], "messages.addChatUser": ["USER_NOT_MUTUAL_CONTACT", "USER_PRIVACY_RESTRICTED"], "channels.createChannel": ["USER_RESTRICTED"], "messages.createChat": ["USER_RESTRICTED"]}, "406": {"auth.checkPhone": ["PHONE_NUMBER_INVALID"], "auth.sendCode": ["PHONE_PASSWORD_FLOOD"]}, "-503": {"auth.resetAuthorizations": ["Timeout"], "contacts.deleteContacts": ["Timeout"], "messages.forwardMessages": ["Timeout"], "messages.getBotCallbackAnswer": ["Timeout"], "messages.getHistory": ["Timeout"], "messages.getInlineBotResults": ["Timeout"], "updates.getState": ["Timeout"]}}, "human_result": {"-429": ["Too many requests"], "ABOUT_TOO_LONG": ["The provided bio is too long"], "ACCESS_TOKEN_EXPIRED": ["Bot token expired"], "ACCESS_TOKEN_INVALID": ["The provided token is not valid"], "ACTIVE_USER_REQUIRED": ["The method is only available to already activated users"], "API_ID_INVALID": ["The api_id/api_hash combination is invalid"], "ARTICLE_TITLE_EMPTY": ["The title of the article is empty"], "AUTH_BYTES_INVALID": ["The provided authorization is invalid"], "AUTH_KEY_PERM_EMPTY": ["The temporary auth key must be binded to the permanent auth key to use these methods."], "AUTH_KEY_UNREGISTERED": ["The authorization key has expired"], "AUTH_RESTART": ["Restart the authorization process"], "BOT_GROUPS_BLOCKED": ["This bot can't be added to groups"], "BOT_INLINE_DISABLED": ["This bot can't be used in inline mode"], "BOT_INVALID": ["This is not a valid bot"], "BOT_METHOD_INVALID": ["This method cannot be run by a bot"], "BOT_MISSING": ["This method can only be run by a bot"], "BOTS_TOO_MUCH": ["There are too many bots in this chat/channel"], "BUTTON_DATA_INVALID": ["The provided button data is invalid"], "BUTTON_TYPE_INVALID": ["The type of one of the buttons you provided is invalid"], "BUTTON_URL_INVALID": ["Button URL invalid"], "CALL_ALREADY_ACCEPTED": ["The call was already accepted"], "CALL_ALREADY_DECLINED": ["The call was already declined"], "CALL_OCCUPY_FAILED": ["The call failed because the user is already making another call"], "CALL_PEER_INVALID": ["The provided call peer object is invalid"], "CALL_PROTOCOL_FLAGS_INVALID": ["Call protocol flags invalid"], "CDN_METHOD_INVALID": ["You can't call this method in a CDN DC"], "CHANNEL_INVALID": ["The provided channel is invalid"], "CHANNEL_PRIVATE": ["You haven't joined this channel/supergroup"], "CHANNEL_PUBLIC_GROUP_NA": ["channel/supergroup not available"], "CHANNELS_TOO_MUCH": ["You have joined too many channels/supergroups"], "CHAT_ABOUT_NOT_MODIFIED": ["About text has not changed"], "CHAT_ADMIN_INVITE_REQUIRED": ["You do not have the rights to do this"], "CHAT_ADMIN_REQUIRED": ["You must be an admin in this chat to do this"], "CHAT_FORBIDDEN": ["You cannot write in this chat"], "CHAT_ID_EMPTY": ["The provided chat ID is empty"], "CHAT_ID_INVALID": ["The provided chat id is invalid"], "CHAT_NOT_MODIFIED": ["The pinned message wasn't modified"], "CHAT_SEND_GIFS_FORBIDDEN": ["You can't send gifs in this chat"], "CHAT_SEND_MEDIA_FORBIDDEN": ["You can't send media in this chat"], "CHAT_SEND_STICKERS_FORBIDDEN": ["You can't send stickers in this chat."], "CHAT_TITLE_EMPTY": ["No chat title provided"], "CHAT_WRITE_FORBIDDEN": ["You can't write in this chat"], "CODE_EMPTY": ["The provided code is empty"], "CODE_HASH_INVALID": ["Code hash invalid"], "CONNECTION_API_ID_INVALID": ["The provided API id is invalid"], "CONNECTION_LANG_PACK_INVALID": ["Language pack invalid"], "CONNECTION_LAYER_INVALID": ["Layer invalid"], "CONTACT_ID_INVALID": ["The provided contact ID is invalid"], "DATA_INVALID": ["Encrypted data invalid"], "DATA_JSON_INVALID": ["The provided JSON data is invalid"], "DATE_EMPTY": ["Date empty"], "DC_ID_INVALID": ["The provided DC ID is invalid"], "DH_G_A_INVALID": ["g_a invalid"], "ENCRYPTED_MESSAGE_INVALID": ["Encrypted message invalid"], "ENCRYPTION_ALREADY_ACCEPTED": ["Secret chat already accepted"], "ENCRYPTION_ALREADY_DECLINED": ["The secret chat was already declined"], "ENCRYPTION_ID_INVALID": ["The provided secret chat ID is invalid"], "ENCRYPTION_OCCUPY_FAILED": ["Internal server error while accepting secret chat"], "ENTITY_MENTION_USER_INVALID": ["You can't use this entity"], "ERROR_TEXT_EMPTY": ["The provided error message is empty"], "EXPORT_CARD_INVALID": ["Provided card is invalid"], "FIELD_NAME_EMPTY": ["The field with the name FIELD_NAME is missing"], "FIELD_NAME_INVALID": ["The field with the name FIELD_NAME is invalid"], "FILE_ID_INVALID": ["The provided file id is invalid"], "FILE_PART_0_MISSING": ["File part 0 missing"], "FILE_PART_EMPTY": ["The provided file part is empty"], "FILE_PART_INVALID": ["The file part number is invalid"], "FILE_PART_LENGTH_INVALID": ["The length of a file part is invalid"], "FILE_PART_SIZE_INVALID": ["The provided file part size is invalid"], "FILE_PARTS_INVALID": ["The number of file parts is invalid"], "FIRSTNAME_INVALID": ["The first name is invalid"], "FLOOD_WAIT_666": ["Spooky af m8"], "GIF_ID_INVALID": ["The provided GIF ID is invalid"], "HASH_INVALID": ["The provided hash is invalid"], "HISTORY_GET_FAILED": ["Fetching of history failed"], "IMAGE_PROCESS_FAILED": ["Failure while processing image"], "INLINE_RESULT_EXPIRED": ["The inline query expired"], "INPUT_CONSTRUCTOR_INVALID": ["The provided constructor is invalid"], "INPUT_FETCH_ERROR": ["An error occurred while deserializing TL parameters"], "INPUT_FETCH_FAIL": ["Failed deserializing TL payload"], "INPUT_LAYER_INVALID": ["The provided layer is invalid"], "INPUT_METHOD_INVALID": ["The provided method is invalid"], "INPUT_REQUEST_TOO_LONG": ["The request is too big"], "INPUT_USER_DEACTIVATED": ["The specified user was deleted"], "INTERDC_1_CALL_ERROR": ["An error occurred while communicating with DC 1"], "INTERDC_1_CALL_RICH_ERROR": ["A rich error occurred while communicating with DC 1"], "INTERDC_2_CALL_ERROR": ["An error occurred while communicating with DC 2"], "INTERDC_2_CALL_RICH_ERROR": ["A rich error occurred while communicating with DC 2"], "INTERDC_3_CALL_ERROR": ["An error occurred while communicating with DC 3"], "INTERDC_3_CALL_RICH_ERROR": ["A rich error occurred while communicating with DC 3"], "INTERDC_4_CALL_ERROR": ["An error occurred while communicating with DC 4"], "INTERDC_4_CALL_RICH_ERROR": ["A rich error occurred while communicating with DC 4"], "INTERDC_5_CALL_ERROR": ["An error occurred while communicating with DC 5"], "INTERDC_5_CALL_RICH_ERROR": ["A rich error occurred while communicating with DC 5"], "INVITE_HASH_EMPTY": ["The invite hash is empty"], "INVITE_HASH_EXPIRED": ["The invite link has expired"], "INVITE_HASH_INVALID": ["The invite hash is invalid"], "LANG_PACK_INVALID": ["The provided language pack is invalid"], "LASTNAME_INVALID": ["The last name is invalid"], "LIMIT_INVALID": ["The provided limit is invalid"], "LOCATION_INVALID": ["The provided location is invalid"], "MAX_ID_INVALID": ["The provided max ID is invalid"], "MD5_CHECKSUM_INVALID": ["The MD5 checksums do not match"], "MEDIA_CAPTION_TOO_LONG": ["The caption is too long"], "MEDIA_EMPTY": ["The provided media object is invalid"], "MEMBER_OCCUPY_PRIMARY_LOC_FAILED": ["Occupation of primary member location failed"], "MESSAGE_AUTHOR_REQUIRED": ["Message author required"], "MESSAGE_DELETE_FORBIDDEN": ["You can't delete one of the messages you tried to delete, most likely because it is a service message."], "MESSAGE_EDIT_TIME_EXPIRED": ["You can't edit this message anymore, too much time has passed since its creation."], "MESSAGE_EMPTY": ["The provided message is empty"], "MESSAGE_ID_INVALID": ["The provided message id is invalid"], "MESSAGE_IDS_EMPTY": ["No message ids were provided"], "MESSAGE_NOT_MODIFIED": ["The message text has not changed"], "MESSAGE_TOO_LONG": ["The provided message is too long"], "MSG_WAIT_FAILED": ["A waiting call returned an error"], "NEED_CHAT_INVALID": ["The provided chat is invalid"], "NEED_MEMBER_INVALID": ["The provided member is invalid"], "NEW_SALT_INVALID": ["The new salt is invalid"], "NEW_SETTINGS_INVALID": ["The new settings are invalid"], "OFFSET_INVALID": ["The provided offset is invalid"], "OFFSET_PEER_ID_INVALID": ["The provided offset peer is invalid"], "PACK_SHORT_NAME_INVALID": ["Short pack name invalid"], "PARTICIPANT_CALL_FAILED": ["Failure while making call"], "PARTICIPANT_VERSION_OUTDATED": ["The other participant does not use an up to date telegram client with support for calls"], "PARTICIPANTS_TOO_FEW": ["Not enough participants"], "PASSWORD_EMPTY": ["The provided password is empty"], "PASSWORD_HASH_INVALID": ["The provided password hash is invalid"], "PEER_FLOOD": ["Too many requests"], "PEER_ID_INVALID": ["The provided peer id is invalid"], "PEER_ID_NOT_SUPPORTED": ["The provided peer ID is not supported"], "PERSISTENT_TIMESTAMP_EMPTY": ["Persistent timestamp empty"], "PERSISTENT_TIMESTAMP_INVALID": ["Persistent timestamp invalid"], "PERSISTENT_TIMESTAMP_OUTDATED": ["Persistent timestamp outdated"], "PHONE_CODE_EMPTY": ["phone_code is missing"], "PHONE_CODE_EXPIRED": ["The phone code you provided has expired, this may happen if it was sent to any chat on telegram (if the code is sent through a telegram chat (not the official account) to avoid it append or prepend to the code some chars)"], "PHONE_CODE_HASH_EMPTY": ["phone_code_hash is missing"], "PHONE_CODE_INVALID": ["The provided phone code is invalid"], "PHONE_NUMBER_APP_SIGNUP_FORBIDDEN": [""], "PHONE_NUMBER_BANNED": ["The provided phone number is banned from telegram"], "PHONE_NUMBER_FLOOD": ["You asked for the code too many times."], "PHONE_NUMBER_INVALID": ["The phone number is invalid"], "PHONE_NUMBER_OCCUPIED": ["The phone number is already in use"], "PHONE_NUMBER_UNOCCUPIED": ["The phone number is not yet being used"], "PHONE_PASSWORD_FLOOD": ["You have tried logging in too many times"], "PHONE_PASSWORD_PROTECTED": ["This phone is password protected"], "PHOTO_CROP_SIZE_SMALL": ["Photo is too small"], "PHOTO_EXT_INVALID": ["The extension of the photo is invalid"], "PHOTO_INVALID_DIMENSIONS": ["The photo dimensions are invalid"], "PRIVACY_KEY_INVALID": ["The privacy key is invalid"], "PTS_CHANGE_EMPTY": ["No PTS change"], "QUERY_ID_EMPTY": ["The query ID is empty"], "QUERY_ID_INVALID": ["The query ID is invalid"], "QUERY_TOO_SHORT": ["The query string is too short"], "RANDOM_ID_DUPLICATE": ["You provided a random ID that was already used"], "RANDOM_ID_INVALID": ["A provided random ID is invalid"], "RANDOM_LENGTH_INVALID": ["Random length invalid"], "RANGES_INVALID": ["Invalid range provided"], "REG_ID_GENERATE_FAILED": ["Failure while generating registration ID"], "REPLY_MARKUP_INVALID": ["The provided reply markup is invalid"], "RESULT_TYPE_INVALID": ["Result type invalid"], "RIGHT_FORBIDDEN": ["Your admin rights do not allow you to do this"], "RPC_CALL_FAIL": ["Telegram is having internal issues, please try again later."], "RPC_MCGET_FAIL": ["Telegram is having internal issues, please try again later."], "RSA_DECRYPT_FAILED": ["Internal RSA decryption failed"], "SEARCH_QUERY_EMPTY": ["The search query is empty"], "SEND_MESSAGE_TYPE_INVALID": ["The message type is invalid"], "SESSION_PASSWORD_NEEDED": ["2FA is enabled, use a password to login"], "SHA256_HASH_INVALID": ["The provided SHA256 hash is invalid"], "START_PARAM_EMPTY": ["The start parameter is empty"], "START_PARAM_INVALID": ["Start parameter invalid"], "STICKER_ID_INVALID": ["The provided sticker ID is invalid"], "STICKER_INVALID": ["The provided sticker is invalid"], "STICKERS_EMPTY": ["No sticker provided"], "STICKERSET_INVALID": ["The provided sticker set is invalid"], "STORAGE_CHECK_FAILED": ["Server storage check failed"], "TEMP_AUTH_KEY_EMPTY": ["No temporary auth key provided"], "Timeout": ["A timeout occurred while fetching data from the bot"], "TMP_PASSWORD_DISABLED": ["The temporary password is disabled"], "TOKEN_INVALID": ["The provided token is invalid"], "TTL_DAYS_INVALID": ["The provided TTL is invalid"], "TYPE_CONSTRUCTOR_INVALID": ["The type constructor is invalid"], "TYPES_EMPTY": ["The types field is empty"], "UNKNOWN_METHOD": ["The method you tried to call cannot be called on non-CDN DCs"], "USER_ADMIN_INVALID": ["You're not an admin"], "USER_ALREADY_PARTICIPANT": ["The user is already in the group"], "USER_BANNED_IN_CHANNEL": ["You're banned from sending messages in supergroups/channels"], "USER_BLOCKED": ["User blocked"], "USER_BOT": ["Bots can only be admins in channels."], "USER_BOT_INVALID": ["This method can only be called by a bot"], "USER_BOT_REQUIRED": ["This method can only be called by a bot"], "USER_CHANNELS_TOO_MUCH": ["One of the users you tried to add is already in too many channels/supergroups"], "USER_CREATOR": ["You can't leave this channel, because you're its creator"], "USER_DEACTIVATED": ["The user was deactivated"], "USER_ID_INVALID": ["The provided user ID is invalid"], "USER_IS_BLOCKED": ["User is blocked"], "USER_IS_BOT": ["Bots can't send messages to other bots"], "USER_KICKED": ["This user was kicked from this supergroup/channel"], "USER_NOT_MUTUAL_CONTACT": ["The provided user is not a mutual contact"], "USER_NOT_PARTICIPANT": ["You're not a member of this supergroup/channel"], "USER_PRIVACY_RESTRICTED": ["The user's privacy settings do not allow you to do this"], "USER_RESTRICTED": ["You're spamreported, you can't create channels or chats."], "USERNAME_INVALID": ["The provided username is not valid"], "USERNAME_NOT_MODIFIED": ["The username was not modified"], "USERNAME_NOT_OCCUPIED": ["The provided username is not occupied"], "USERNAME_OCCUPIED": ["The provided username is already occupied"], "USERS_TOO_FEW": ["Not enough users (to create a chat, for example)"], "USERS_TOO_MUCH": ["The maximum number of users has been exceeded (to create a chat, for example)"], "WEBPAGE_CURL_FAILED": ["Failure while fetching the webpage with cURL"], "YOU_BLOCKED_USER": ["You blocked this user"]}} \ No newline at end of file From be9358282a06b456ef0112a64c2d11e4bd89abcc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 17:29:45 +0200 Subject: [PATCH 15/23] Generate and fetch new errors from setup.py --- .gitignore | 2 +- setup.py | 18 +++++++++++++++--- telethon_generator/error_generator.py | 8 +++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index dd5de4c3..156d23e3 100755 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ telethon/tl/functions/ telethon/tl/types/ telethon/tl/all_tlobjects.py -telethon/tl/errors/rpc_error_list.py +telethon/errors/rpc_error_list.py # User session *.session diff --git a/setup.py b/setup.py index 695ad1a5..3ac7a90b 100755 --- a/setup.py +++ b/setup.py @@ -42,17 +42,25 @@ class TempWorkDir: os.chdir(self.original) +ERROR_LIST = 'telethon/errors/rpc_error_list.py' +ERRORS_JSON = 'telethon_generator/errors.json' +ERRORS_DESC = 'telethon_generator/error_descriptions' +SCHEME_TL = 'telethon_generator/scheme.tl' +IMPORT_DEPTH = 2 + + def gen_tl(): from telethon_generator.tl_generator import TLGenerator + from telethon_generator.error_generator import generate_code generator = TLGenerator('telethon/tl') if generator.tlobjects_exist(): print('Detected previous TLObjects. Cleaning...') generator.clean_tlobjects() print('Generating TLObjects...') - generator.generate_tlobjects( - 'telethon_generator/scheme.tl', import_depth=2 - ) + generator.generate_tlobjects(SCHEME_TL, import_depth=IMPORT_DEPTH) + print('Generating errors...') + generate_code(ERROR_LIST, json_file=ERRORS_JSON, errors_desc=ERRORS_DESC) print('Done.') @@ -80,6 +88,10 @@ def main(): for x in ('build', 'dist', 'Telethon.egg-info'): rmtree(x, ignore_errors=True) + if len(argv) >= 2 and argv[1] == 'fetch_errors': + from telethon_generator.error_generator import fetch_errors + fetch_errors(ERRORS_JSON) + else: if not TelegramClient: gen_tl() diff --git a/telethon_generator/error_generator.py b/telethon_generator/error_generator.py index fb5d0ef7..81bcda5c 100644 --- a/telethon_generator/error_generator.py +++ b/telethon_generator/error_generator.py @@ -4,8 +4,6 @@ import urllib.request from collections import defaultdict URL = 'https://rpc.pwrtelegram.xyz/?all' -OUTPUT = '../telethon/errors/rpc_error_list.py' -JSON_OUTPUT = 'errors.json' known_base_classes = { 303: 'InvalidDCError', @@ -26,7 +24,7 @@ known_codes = { } -def fetch_errors(url=URL, output=JSON_OUTPUT): +def fetch_errors(output, url=URL): print('Opening a connection to', url, '...') r = urllib.request.urlopen(url) print('Checking response...') @@ -78,7 +76,7 @@ def write_error(f, code, name, desc, capture_name): f.write(')\n') -def generate_code(json_file=JSON_OUTPUT, output=OUTPUT): +def generate_code(output, json_file, errors_desc): with open(json_file, encoding='utf-8') as f: data = json.load(f) @@ -113,7 +111,7 @@ def generate_code(json_file=JSON_OUTPUT, output=OUTPUT): # Prefer the descriptions that are related with Telethon way of coding to # those that PWRTelegram's API provides. telethon_descriptions = {} - with open('error_descriptions', encoding='utf-8') as f: + with open(errors_desc, encoding='utf-8') as f: for line in f: line = line.strip() if line and not line.startswith('#'): From 83595a0e2d8d5d3f0e7ac642e2a8b8398845d353 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 17:32:30 +0200 Subject: [PATCH 16/23] Use more constants in setup.py --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3ac7a90b..13be0144 100755 --- a/setup.py +++ b/setup.py @@ -46,13 +46,14 @@ ERROR_LIST = 'telethon/errors/rpc_error_list.py' ERRORS_JSON = 'telethon_generator/errors.json' ERRORS_DESC = 'telethon_generator/error_descriptions' SCHEME_TL = 'telethon_generator/scheme.tl' +GENERATOR_DIR = 'telethon/tl' IMPORT_DEPTH = 2 def gen_tl(): from telethon_generator.tl_generator import TLGenerator from telethon_generator.error_generator import generate_code - generator = TLGenerator('telethon/tl') + generator = TLGenerator(GENERATOR_DIR) if generator.tlobjects_exist(): print('Detected previous TLObjects. Cleaning...') generator.clean_tlobjects() @@ -71,7 +72,7 @@ def main(): elif len(argv) >= 2 and argv[1] == 'clean_tl': from telethon_generator.tl_generator import TLGenerator print('Cleaning...') - TLGenerator('telethon/tl').clean_tlobjects() + TLGenerator(GENERATOR_DIR).clean_tlobjects() print('Done.') elif len(argv) >= 2 and argv[1] == 'pypi': From 033119e9b8f1b56f19b897f89d35c2b4d975556f Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 22:07:45 +0200 Subject: [PATCH 17/23] Make MtProtoSender._need_confirmation a set This will avoid adding duplicated items to it --- telethon/network/mtproto_sender.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 61e60484..8c73390f 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -39,7 +39,7 @@ class MtProtoSender: self._logger = logging.getLogger(__name__) # Message IDs that need confirmation - self._need_confirmation = [] + self._need_confirmation = set() # Requests (as msg_id: Message) sent waiting to be received self._pending_receive = {} @@ -74,7 +74,7 @@ class MtProtoSender: # Pack everything in the same container if we need to send AckRequests if self._need_confirmation: messages.append( - TLMessage(self.session, MsgsAck(self._need_confirmation)) + TLMessage(self.session, MsgsAck(list(self._need_confirmation))) ) self._need_confirmation.clear() @@ -183,7 +183,7 @@ class MtProtoSender: ) return False - self._need_confirmation.append(msg_id) + self._need_confirmation.add(msg_id) code = reader.read_int(signed=False) reader.seek(-4) From 1a91c024fcc29235a1f1e03ee755b076dc2dec4f Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 22:12:03 +0200 Subject: [PATCH 18/23] Revert 63dfb1e as many updates were being dropped --- telethon/network/mtproto_sender.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 8c73390f..3f98c5f3 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -174,15 +174,6 @@ class MtProtoSender: """ # TODO Check salt, session_id and sequence_number - if msg_id in self._need_confirmation: - # We're yet to acknowledge this message already, so just drop it - # as we are already aware of it. TODO Should we force acknowledging - # all the self._need_confirmation IDs? - self._logger.debug( - 'Ignoring message pending of acknowledge: {}'.format(msg_id) - ) - return False - self._need_confirmation.add(msg_id) code = reader.read_int(signed=False) From 2782a08ed03f0bfbf148284d176e0e4ca3c84b9e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 22:44:00 +0200 Subject: [PATCH 19/23] Add note for future self when handling gzip packed data --- telethon/network/mtproto_sender.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/telethon/network/mtproto_sender.py b/telethon/network/mtproto_sender.py index 3f98c5f3..50281e9b 100644 --- a/telethon/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -412,6 +412,11 @@ class MtProtoSender: def _handle_gzip_packed(self, msg_id, sequence, reader, state): self._logger.debug('Handling gzip packed data') with BinaryReader(GzipPacked.read(reader)) as compressed_reader: + # We are reentering process_msg, which seemingly the same msg_id + # to the self._need_confirmation set. Remove it from there first + # to avoid any future conflicts (i.e. if we "ignore" messages + # that we are already aware of, see 1a91c02 and old 63dfb1e) + self._need_confirmation -= {msg_id} return self._process_msg(msg_id, sequence, compressed_reader, state) # endregion From d70811b693f7bd4f899fa655e68498012696b29d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 23:30:02 +0200 Subject: [PATCH 20/23] Fix infinite loop when invoking on update handlers (fix #336) Every update that hadn't been acknowledged on the main connection yet would be resent on any new connection. These new connections are made temporary when invoking anything from any thread that's not the main thread. It would also process all the updates, hence, Telegram would be resending these not-acknowledged updates to the temporary connection, and the updates would be processed again, then the update handler would react to the duplicated updates over and over. To fix this, simply don't process updates on the temporary thread at all. With this reasoning, if we don't acknowledge updates on the temporary connections, Telegram will resend them on the main connection, so we should not lose any. --- telethon/telegram_bare_client.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 0b31c4c5..2a5ce4f5 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -432,9 +432,18 @@ class TelegramBareClient: on_main_thread = threading.get_ident() == self._main_thread_ident if on_main_thread or self._on_read_thread(): sender = self._sender + update_state = self.updates else: sender = self._sender.clone() sender.connect() + # We're on another connection, Telegram will resend all the + # updates that we haven't acknowledged (potentially entering + # an infinite loop if we're calling this in response to an + # update event, as it would be received again and again). So + # to avoid this we will simply not process updates on these + # new temporary connections, as they will be sent and later + # acknowledged over the main connection. + update_state = None # We should call receive from this thread if there's no background # thread reading or if the server disconnected us and we're trying @@ -447,7 +456,9 @@ class TelegramBareClient: if self._background_error and on_main_thread: raise self._background_error - result = self._invoke(sender, call_receive, *requests) + result = self._invoke( + sender, call_receive, update_state, *requests + ) if result is not None: return result @@ -459,7 +470,7 @@ class TelegramBareClient: # Let people use client.invoke(SomeRequest()) instead client(...) invoke = __call__ - def _invoke(self, sender, call_receive, *requests): + def _invoke(self, sender, call_receive, update_state, *requests): try: # Ensure that we start with no previous errors (i.e. resending) for x in requests: @@ -479,7 +490,7 @@ class TelegramBareClient: ) else: while not all(x.confirm_received.is_set() for x in requests): - sender.receive(update_state=self.updates) + sender.receive(update_state=update_state) except TimeoutError: pass # We will just retry @@ -526,7 +537,7 @@ class TelegramBareClient: # be on the very first connection (not authorized, not running), # but may be an issue for people who actually travel? self._reconnect(new_dc=e.new_dc) - return self._invoke(sender, call_receive, *requests) + return self._invoke(sender, call_receive, update_state, *requests) except ServerError as e: # Telegram is having some issues, just retry From 5cdf92e50960300c1cb8514afc1362fb67618e4a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 20 Oct 2017 23:33:08 +0200 Subject: [PATCH 21/23] Update to v0.15.3 --- telethon/telegram_bare_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 2a5ce4f5..53edb008 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -58,7 +58,7 @@ class TelegramBareClient: """ # Current TelegramClient version - __version__ = '0.15.2' + __version__ = '0.15.3' # TODO Make this thread-safe, all connections share the same DC _dc_options = None From 9937d58a2d78a7e85c24106c2242f68bcb582972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20G=C3=B6tzer?= Date: Sat, 21 Oct 2017 00:43:26 +0200 Subject: [PATCH 22/23] Remove f-strings from codegen to support py <3.6 (#366) --- telethon_generator/error_generator.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/telethon_generator/error_generator.py b/telethon_generator/error_generator.py index 81bcda5c..feb32b5a 100644 --- a/telethon_generator/error_generator.py +++ b/telethon_generator/error_generator.py @@ -59,20 +59,16 @@ def get_class_name(error_code): def write_error(f, code, name, desc, capture_name): f.write( - f'\n' - f'\n' - f'class {name}({get_class_name(code)}):\n' - f' def __init__(self, **kwargs):\n' - f' ' + '\n\nclass {}({}):\n def __init__(self, **kwargs):\n ' + ''.format(name, get_class_name(code)) ) if capture_name: f.write( - f"self.{capture_name} = int(kwargs.get('capture', 0))\n" - f" " + "self.{} = int(kwargs.get('capture', 0))\n ".format(capture_name) ) - f.write(f'super(Exception, self).__init__(self, {repr(desc)}') + f.write('super(Exception, self).__init__(self, {}'.format(repr(desc))) if capture_name: - f.write(f'.format(self.{capture_name})') + f.write('.format(self.{})'.format(capture_name)) f.write(')\n') @@ -132,15 +128,12 @@ def generate_code(output, json_file, errors_desc): # Everything ready, generate the code with open(output, 'w', encoding='utf-8') as f: f.write( - f'from .rpc_base_errors import RPCError, BadMessageError, ' - f'{", ".join(known_base_classes.values())}\n' + 'from .rpc_base_errors import RPCError, BadMessageError, {}\n'.format( + ", ".join(known_base_classes.values())) ) for code, cls in needed_base_classes: f.write( - f'\n' - f'\n' - f'class {cls}(RPCError):\n' - f' code = {code}\n' + '\n\nclass {}(RPCError):\n code = {}\n'.format(cls, code) ) patterns = [] # Save this dictionary later in the generated code @@ -166,5 +159,6 @@ def generate_code(output, json_file, errors_desc): f.write('\n\nrpc_errors_all = {\n') for pattern, name in patterns: - f.write(f' {repr(pattern)}: {name},\n') + f.write(' {}: {},\n'.format(repr(pattern), name)) f.write('}\n') + From 7596f2b797dab2307f6c6d7193be6485278adf61 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 21 Oct 2017 13:47:54 +0200 Subject: [PATCH 23/23] Fix and enhance "no workers set" warning --- telethon/telegram_bare_client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 53edb008..6e7029c8 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -756,8 +756,12 @@ class TelegramBareClient: def add_update_handler(self, handler): """Adds an update handler (a function which takes a TLObject, an update, as its parameter) and listens for updates""" - if not self.updates.get_workers: - warnings.warn("There are no update workers running, so adding an update handler will have no effect.") + if self.updates.workers is None: + warnings.warn( + "You have not setup any workers, so you won't receive updates." + " Pass update_workers=4 when creating the TelegramClient," + " or set client.self.updates.workers = 4" + ) self.updates.handlers.append(handler)