Generate errors from PWRTelegram's API

This commit is contained in:
Lonami Exo
2017-10-20 15:44:43 +02:00
parent f37b9ed20e
commit 38ccd6d1d9
10 changed files with 260 additions and 642 deletions

View File

@@ -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

View File

@@ -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')

File diff suppressed because one or more lines are too long