mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-03-10 06:05:47 +03:00
Merge branch 'master' into asyncio
This commit is contained in:
commit
335bc6a789
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
||||||
telethon/tl/functions/
|
telethon/tl/functions/
|
||||||
telethon/tl/types/
|
telethon/tl/types/
|
||||||
telethon/tl/all_tlobjects.py
|
telethon/tl/all_tlobjects.py
|
||||||
|
telethon/errors/rpc_error_list.py
|
||||||
|
|
||||||
# User session
|
# User session
|
||||||
*.session
|
*.session
|
||||||
|
|
23
setup.py
23
setup.py
|
@ -42,17 +42,26 @@ class TempWorkDir:
|
||||||
os.chdir(self.original)
|
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'
|
||||||
|
GENERATOR_DIR = 'telethon/tl'
|
||||||
|
IMPORT_DEPTH = 2
|
||||||
|
|
||||||
|
|
||||||
def gen_tl():
|
def gen_tl():
|
||||||
from telethon_generator.tl_generator import TLGenerator
|
from telethon_generator.tl_generator import TLGenerator
|
||||||
generator = TLGenerator('telethon/tl')
|
from telethon_generator.error_generator import generate_code
|
||||||
|
generator = TLGenerator(GENERATOR_DIR)
|
||||||
if generator.tlobjects_exist():
|
if generator.tlobjects_exist():
|
||||||
print('Detected previous TLObjects. Cleaning...')
|
print('Detected previous TLObjects. Cleaning...')
|
||||||
generator.clean_tlobjects()
|
generator.clean_tlobjects()
|
||||||
|
|
||||||
print('Generating TLObjects...')
|
print('Generating TLObjects...')
|
||||||
generator.generate_tlobjects(
|
generator.generate_tlobjects(SCHEME_TL, import_depth=IMPORT_DEPTH)
|
||||||
'telethon_generator/scheme.tl', import_depth=2
|
print('Generating errors...')
|
||||||
)
|
generate_code(ERROR_LIST, json_file=ERRORS_JSON, errors_desc=ERRORS_DESC)
|
||||||
print('Done.')
|
print('Done.')
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,7 +72,7 @@ def main():
|
||||||
elif len(argv) >= 2 and argv[1] == 'clean_tl':
|
elif len(argv) >= 2 and argv[1] == 'clean_tl':
|
||||||
from telethon_generator.tl_generator import TLGenerator
|
from telethon_generator.tl_generator import TLGenerator
|
||||||
print('Cleaning...')
|
print('Cleaning...')
|
||||||
TLGenerator('telethon/tl').clean_tlobjects()
|
TLGenerator(GENERATOR_DIR).clean_tlobjects()
|
||||||
print('Done.')
|
print('Done.')
|
||||||
|
|
||||||
elif len(argv) >= 2 and argv[1] == 'pypi':
|
elif len(argv) >= 2 and argv[1] == 'pypi':
|
||||||
|
@ -80,6 +89,10 @@ def main():
|
||||||
for x in ('build', 'dist', 'Telethon.egg-info'):
|
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||||
rmtree(x, ignore_errors=True)
|
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:
|
else:
|
||||||
if not TelegramClient:
|
if not TelegramClient:
|
||||||
gen_tl()
|
gen_tl()
|
||||||
|
|
|
@ -8,15 +8,8 @@ from .common import (
|
||||||
CdnFileTamperedError
|
CdnFileTamperedError
|
||||||
)
|
)
|
||||||
|
|
||||||
from .rpc_errors import (
|
# This imports the base errors too, as they're imported there
|
||||||
RPCError, InvalidDCError, BadRequestError, UnauthorizedError,
|
from .rpc_error_list import *
|
||||||
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 *
|
|
||||||
|
|
||||||
|
|
||||||
def report_error(code, message, report_method):
|
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)
|
args=(code, message, report_method)
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
errors = {
|
# Try to get the error by direct look-up, otherwise regex
|
||||||
303: rpc_errors_303_all,
|
# TODO Maybe regexes could live in a separate dictionary?
|
||||||
400: rpc_errors_400_all,
|
cls = rpc_errors_all.get(message, None)
|
||||||
401: rpc_errors_401_all,
|
if cls:
|
||||||
420: rpc_errors_420_all
|
return cls()
|
||||||
}.get(code, None)
|
|
||||||
|
|
||||||
if errors is not None:
|
for msg_regex, cls in rpc_errors_all.items():
|
||||||
for msg, cls in errors.items():
|
m = re.match(msg_regex, message)
|
||||||
m = re.match(msg, message)
|
|
||||||
if m:
|
if m:
|
||||||
extra = int(m.group(1)) if m.groups() else None
|
capture = int(m.group(1)) if m.groups() else None
|
||||||
return cls(extra=extra)
|
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)
|
return ForbiddenError(message)
|
||||||
|
|
||||||
elif code == 404:
|
if code == 404:
|
||||||
return NotFoundError(message)
|
return NotFoundError(message)
|
||||||
|
|
||||||
elif code == 500:
|
if code == 500:
|
||||||
return ServerError(message)
|
return ServerError(message)
|
||||||
|
|
||||||
return RPCError('{} (code {})'.format(message, code))
|
return RPCError('{} (code {})'.format(message, code))
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -42,7 +42,7 @@ async def _do_authentication(connection):
|
||||||
req_pq_request = ReqPqRequest(
|
req_pq_request = ReqPqRequest(
|
||||||
nonce=int.from_bytes(os.urandom(16), 'big', signed=True)
|
nonce=int.from_bytes(os.urandom(16), 'big', signed=True)
|
||||||
)
|
)
|
||||||
await sender.send(req_pq_request.to_bytes())
|
await sender.send(bytes(req_pq_request))
|
||||||
with BinaryReader(await sender.receive()) as reader:
|
with BinaryReader(await sender.receive()) as reader:
|
||||||
req_pq_request.on_response(reader)
|
req_pq_request.on_response(reader)
|
||||||
|
|
||||||
|
@ -60,12 +60,12 @@ async def _do_authentication(connection):
|
||||||
p, q = rsa.get_byte_array(min(p, q)), rsa.get_byte_array(max(p, q))
|
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)
|
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,
|
pq=rsa.get_byte_array(pq), p=p, q=q,
|
||||||
nonce=res_pq.nonce,
|
nonce=res_pq.nonce,
|
||||||
server_nonce=res_pq.server_nonce,
|
server_nonce=res_pq.server_nonce,
|
||||||
new_nonce=new_nonce
|
new_nonce=new_nonce
|
||||||
).to_bytes()
|
))
|
||||||
|
|
||||||
# sha_digest + data + random_bytes
|
# sha_digest + data + random_bytes
|
||||||
cipher_text, target_fingerprint = None, None
|
cipher_text, target_fingerprint = None, None
|
||||||
|
@ -90,7 +90,7 @@ async def _do_authentication(connection):
|
||||||
public_key_fingerprint=target_fingerprint,
|
public_key_fingerprint=target_fingerprint,
|
||||||
encrypted_data=cipher_text
|
encrypted_data=cipher_text
|
||||||
)
|
)
|
||||||
await sender.send(req_dh_params.to_bytes())
|
await sender.send(bytes(req_dh_params))
|
||||||
|
|
||||||
# Step 2 response: DH Exchange
|
# Step 2 response: DH Exchange
|
||||||
with BinaryReader(await sender.receive()) as reader:
|
with BinaryReader(await sender.receive()) as reader:
|
||||||
|
@ -138,12 +138,12 @@ async def _do_authentication(connection):
|
||||||
gab = pow(g_a, b, dh_prime)
|
gab = pow(g_a, b, dh_prime)
|
||||||
|
|
||||||
# Prepare client DH Inner Data
|
# Prepare client DH Inner Data
|
||||||
client_dh_inner = ClientDHInnerData(
|
client_dh_inner = bytes(ClientDHInnerData(
|
||||||
nonce=res_pq.nonce,
|
nonce=res_pq.nonce,
|
||||||
server_nonce=res_pq.server_nonce,
|
server_nonce=res_pq.server_nonce,
|
||||||
retry_id=0, # TODO Actual retry ID
|
retry_id=0, # TODO Actual retry ID
|
||||||
g_b=rsa.get_byte_array(gb)
|
g_b=rsa.get_byte_array(gb)
|
||||||
).to_bytes()
|
))
|
||||||
|
|
||||||
client_dh_inner_hashed = sha1(client_dh_inner).digest() + client_dh_inner
|
client_dh_inner_hashed = sha1(client_dh_inner).digest() + client_dh_inner
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ async def _do_authentication(connection):
|
||||||
server_nonce=res_pq.server_nonce,
|
server_nonce=res_pq.server_nonce,
|
||||||
encrypted_data=client_dh_encrypted,
|
encrypted_data=client_dh_encrypted,
|
||||||
)
|
)
|
||||||
await sender.send(set_client_dh.to_bytes())
|
await sender.send(bytes(set_client_dh))
|
||||||
|
|
||||||
# Step 3 response: Complete DH Exchange
|
# Step 3 response: Complete DH Exchange
|
||||||
with BinaryReader(await sender.receive()) as reader:
|
with BinaryReader(await sender.receive()) as reader:
|
||||||
|
|
|
@ -39,7 +39,7 @@ class MtProtoSender:
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Message IDs that need confirmation
|
# Message IDs that need confirmation
|
||||||
self._need_confirmation = []
|
self._need_confirmation = set()
|
||||||
|
|
||||||
# Requests (as msg_id: Message) sent waiting to be received
|
# Requests (as msg_id: Message) sent waiting to be received
|
||||||
self._pending_receive = {}
|
self._pending_receive = {}
|
||||||
|
@ -74,7 +74,7 @@ class MtProtoSender:
|
||||||
# Pack everything in the same container if we need to send AckRequests
|
# Pack everything in the same container if we need to send AckRequests
|
||||||
if self._need_confirmation:
|
if self._need_confirmation:
|
||||||
messages.append(
|
messages.append(
|
||||||
TLMessage(self.session, MsgsAck(self._need_confirmation))
|
TLMessage(self.session, MsgsAck(list(self._need_confirmation)))
|
||||||
)
|
)
|
||||||
self._need_confirmation.clear()
|
self._need_confirmation.clear()
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class MtProtoSender:
|
||||||
|
|
||||||
plain_text = \
|
plain_text = \
|
||||||
struct.pack('<QQ', self.session.salt, self.session.id) \
|
struct.pack('<QQ', self.session.salt, self.session.id) \
|
||||||
+ message.to_bytes()
|
+ bytes(message)
|
||||||
|
|
||||||
msg_key = utils.calc_msg_key(plain_text)
|
msg_key = utils.calc_msg_key(plain_text)
|
||||||
key_id = struct.pack('<Q', self.session.auth_key.key_id)
|
key_id = struct.pack('<Q', self.session.auth_key.key_id)
|
||||||
|
@ -174,7 +174,7 @@ class MtProtoSender:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO Check salt, session_id and sequence_number
|
# TODO Check salt, session_id and sequence_number
|
||||||
self._need_confirmation.append(msg_id)
|
self._need_confirmation.add(msg_id)
|
||||||
|
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
reader.seek(-4)
|
reader.seek(-4)
|
||||||
|
@ -217,7 +217,7 @@ class MtProtoSender:
|
||||||
r = self._pop_request_of_type(msg_id, LogOutRequest)
|
r = self._pop_request_of_type(msg_id, LogOutRequest)
|
||||||
if r:
|
if r:
|
||||||
r.result = True # Telegram won't send this value
|
r.result = True # Telegram won't send this value
|
||||||
r.confirm_received()
|
r.confirm_received.set()
|
||||||
self._logger.debug('Message ack confirmed', r)
|
self._logger.debug('Message ack confirmed', r)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -261,7 +261,7 @@ class MtProtoSender:
|
||||||
|
|
||||||
def _clear_all_pending(self):
|
def _clear_all_pending(self):
|
||||||
for r in self._pending_receive.values():
|
for r in self._pending_receive.values():
|
||||||
r.confirm_received.set()
|
r.request.confirm_received.set()
|
||||||
self._pending_receive.clear()
|
self._pending_receive.clear()
|
||||||
|
|
||||||
async def _handle_pong(self, msg_id, sequence, reader):
|
async def _handle_pong(self, msg_id, sequence, reader):
|
||||||
|
@ -303,6 +303,7 @@ class MtProtoSender:
|
||||||
self.session.salt = struct.unpack(
|
self.session.salt = struct.unpack(
|
||||||
'<Q', struct.pack('<q', bad_salt.new_server_salt)
|
'<Q', struct.pack('<q', bad_salt.new_server_salt)
|
||||||
)[0]
|
)[0]
|
||||||
|
self.session.save()
|
||||||
|
|
||||||
request = self._pop_request(bad_salt.bad_msg_id)
|
request = self._pop_request(bad_salt.bad_msg_id)
|
||||||
if request:
|
if request:
|
||||||
|
@ -411,6 +412,11 @@ class MtProtoSender:
|
||||||
async def _handle_gzip_packed(self, msg_id, sequence, reader, state):
|
async def _handle_gzip_packed(self, msg_id, sequence, reader, state):
|
||||||
self._logger.debug('Handling gzip packed data')
|
self._logger.debug('Handling gzip packed data')
|
||||||
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
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 await self._process_msg(msg_id, sequence, compressed_reader, state)
|
return await self._process_msg(msg_id, sequence, compressed_reader, state)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -56,7 +56,7 @@ class TelegramBareClient:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Current TelegramClient version
|
# Current TelegramClient version
|
||||||
__version__ = '0.15.2'
|
__version__ = '0.15.3'
|
||||||
|
|
||||||
# TODO Make this thread-safe, all connections share the same DC
|
# TODO Make this thread-safe, all connections share the same DC
|
||||||
_dc_options = None
|
_dc_options = None
|
||||||
|
@ -124,7 +124,7 @@ class TelegramBareClient:
|
||||||
self._user_connected = False
|
self._user_connected = False
|
||||||
|
|
||||||
# Save whether the user is authorized here (a.k.a. logged in)
|
# 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
|
# Uploaded files cache so subsequent calls are instant
|
||||||
self._upload_cache = {}
|
self._upload_cache = {}
|
||||||
|
@ -198,12 +198,14 @@ class TelegramBareClient:
|
||||||
# another data center and this would raise UserMigrateError)
|
# another data center and this would raise UserMigrateError)
|
||||||
# to also assert whether the user is logged in or not.
|
# to also assert whether the user is logged in or not.
|
||||||
self._user_connected = True
|
self._user_connected = True
|
||||||
if _sync_updates and not _cdn:
|
if self._authorized is None and _sync_updates and not _cdn:
|
||||||
try:
|
try:
|
||||||
await self.sync_updates()
|
await self.sync_updates()
|
||||||
self._set_connected_and_authorized()
|
self._set_connected_and_authorized()
|
||||||
except UnauthorizedError:
|
except UnauthorizedError:
|
||||||
self._authorized = False
|
self._authorized = False
|
||||||
|
elif self._authorized:
|
||||||
|
self._set_connected_and_authorized()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -383,6 +385,8 @@ class TelegramBareClient:
|
||||||
|
|
||||||
# TODO Determine the sender to be used (main or a new connection)
|
# TODO Determine the sender to be used (main or a new connection)
|
||||||
sender = self._sender # .clone(), .connect()
|
sender = self._sender # .clone(), .connect()
|
||||||
|
# We're on the same connection so no need to pass update_state=None
|
||||||
|
# to avoid getting messages that we haven't acknowledged yet.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for _ in range(retries):
|
for _ in range(retries):
|
||||||
|
@ -426,10 +430,7 @@ class TelegramBareClient:
|
||||||
else:
|
else:
|
||||||
while self._user_connected and not await self._reconnect():
|
while self._user_connected and not await self._reconnect():
|
||||||
sleep(0.1) # Retry forever until we can send the request
|
sleep(0.1) # Retry forever until we can send the request
|
||||||
|
return None
|
||||||
finally:
|
|
||||||
if sender != self._sender:
|
|
||||||
sender.disconnect()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raise next(x.rpc_error for x in requests if x.rpc_error)
|
raise next(x.rpc_error for x in requests if x.rpc_error)
|
||||||
|
@ -673,13 +674,14 @@ class TelegramBareClient:
|
||||||
def add_update_handler(self, handler):
|
def add_update_handler(self, handler):
|
||||||
"""Adds an update handler (a function which takes a TLObject,
|
"""Adds an update handler (a function which takes a TLObject,
|
||||||
an update, as its parameter) and listens for updates"""
|
an update, as its parameter) and listens for updates"""
|
||||||
if not self.updates.get_workers:
|
if self.updates.workers is None:
|
||||||
warnings.warn("There are no update workers running, so adding an update handler will have no effect.")
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
sync = not self.updates.handlers
|
|
||||||
self.updates.handlers.append(handler)
|
self.updates.handlers.append(handler)
|
||||||
if sync:
|
|
||||||
self.sync_updates()
|
|
||||||
|
|
||||||
def remove_update_handler(self, handler):
|
def remove_update_handler(self, handler):
|
||||||
self.updates.handlers.remove(handler)
|
self.updates.handlers.remove(handler)
|
||||||
|
|
|
@ -13,21 +13,21 @@ class GzipPacked(TLObject):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def gzip_if_smaller(request):
|
def gzip_if_smaller(request):
|
||||||
"""Calls request.to_bytes(), and based on a certain threshold,
|
"""Calls bytes(request), and based on a certain threshold,
|
||||||
optionally gzips the resulting data. If the gzipped data is
|
optionally gzips the resulting data. If the gzipped data is
|
||||||
smaller than the original byte array, this is returned instead.
|
smaller than the original byte array, this is returned instead.
|
||||||
|
|
||||||
Note that this only applies to content related requests.
|
Note that this only applies to content related requests.
|
||||||
"""
|
"""
|
||||||
data = request.to_bytes()
|
data = bytes(request)
|
||||||
# TODO This threshold could be configurable
|
# TODO This threshold could be configurable
|
||||||
if request.content_related and len(data) > 512:
|
if request.content_related and len(data) > 512:
|
||||||
gzipped = GzipPacked(data).to_bytes()
|
gzipped = bytes(GzipPacked(data))
|
||||||
return gzipped if len(gzipped) < len(data) else data
|
return gzipped if len(gzipped) < len(data) else data
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def to_bytes(self):
|
def __bytes__(self):
|
||||||
# TODO Maybe compress level could be an option
|
# TODO Maybe compress level could be an option
|
||||||
return struct.pack('<I', GzipPacked.CONSTRUCTOR_ID) + \
|
return struct.pack('<I', GzipPacked.CONSTRUCTOR_ID) + \
|
||||||
TLObject.serialize_bytes(gzip.compress(self.data))
|
TLObject.serialize_bytes(gzip.compress(self.data))
|
||||||
|
|
|
@ -11,10 +11,10 @@ class MessageContainer(TLObject):
|
||||||
self.content_related = False
|
self.content_related = False
|
||||||
self.messages = messages
|
self.messages = messages
|
||||||
|
|
||||||
def to_bytes(self):
|
def __bytes__(self):
|
||||||
return struct.pack(
|
return struct.pack(
|
||||||
'<Ii', MessageContainer.CONSTRUCTOR_ID, len(self.messages)
|
'<Ii', MessageContainer.CONSTRUCTOR_ID, len(self.messages)
|
||||||
) + b''.join(m.to_bytes() for m in self.messages)
|
) + b''.join(bytes(m) for m in self.messages)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def iter_read(reader):
|
def iter_read(reader):
|
||||||
|
|
|
@ -12,6 +12,6 @@ class TLMessage(TLObject):
|
||||||
self.seq_no = session.generate_sequence(request.content_related)
|
self.seq_no = session.generate_sequence(request.content_related)
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
def to_bytes(self):
|
def __bytes__(self):
|
||||||
body = GzipPacked.gzip_if_smaller(self.request)
|
body = GzipPacked.gzip_if_smaller(self.request)
|
||||||
return struct.pack('<qii', self.msg_id, self.seq_no, len(body)) + body
|
return struct.pack('<qii', self.msg_id, self.seq_no, len(body)) + body
|
||||||
|
|
|
@ -125,7 +125,7 @@ class TLObject:
|
||||||
def to_dict(self, recursive=True):
|
def to_dict(self, recursive=True):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def to_bytes(self):
|
def __bytes__(self):
|
||||||
return b''
|
return b''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
65
telethon_generator/error_descriptions
Normal file
65
telethon_generator/error_descriptions
Normal 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
|
164
telethon_generator/error_generator.py
Normal file
164
telethon_generator/error_generator.py
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
URL = 'https://rpc.pwrtelegram.xyz/?all'
|
||||||
|
|
||||||
|
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(output, url=URL):
|
||||||
|
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(
|
||||||
|
'\n\nclass {}({}):\n def __init__(self, **kwargs):\n '
|
||||||
|
''.format(name, get_class_name(code))
|
||||||
|
)
|
||||||
|
if capture_name:
|
||||||
|
f.write(
|
||||||
|
"self.{} = int(kwargs.get('capture', 0))\n ".format(capture_name)
|
||||||
|
)
|
||||||
|
f.write('super(Exception, self).__init__(self, {}'.format(repr(desc)))
|
||||||
|
if capture_name:
|
||||||
|
f.write('.format(self.{})'.format(capture_name))
|
||||||
|
f.write(')\n')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_code(output, json_file, errors_desc):
|
||||||
|
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(errors_desc, 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(
|
||||||
|
'from .rpc_base_errors import RPCError, BadMessageError, {}\n'.format(
|
||||||
|
", ".join(known_base_classes.values()))
|
||||||
|
)
|
||||||
|
for code, cls in needed_base_classes:
|
||||||
|
f.write(
|
||||||
|
'\n\nclass {}(RPCError):\n code = {}\n'.format(cls, code)
|
||||||
|
)
|
||||||
|
|
||||||
|
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(' {}: {},\n'.format(repr(pattern), name))
|
||||||
|
f.write('}\n')
|
||||||
|
|
1
telethon_generator/errors.json
Normal file
1
telethon_generator/errors.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -98,12 +98,17 @@ class TLObject:
|
||||||
|
|
||||||
def class_name(self):
|
def class_name(self):
|
||||||
"""Gets the class name following the Python style guidelines"""
|
"""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
|
# 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('_', '')
|
result = result[:1].upper() + result[1:].replace('_', '')
|
||||||
# If it's a function, let it end with "Request" to identify them
|
# If it's a function, let it end with "Request" to identify them
|
||||||
if self.is_function:
|
if is_function:
|
||||||
result += 'Request'
|
result += 'Request'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -192,6 +197,7 @@ class TLArg:
|
||||||
# Default values
|
# Default values
|
||||||
self.is_vector = False
|
self.is_vector = False
|
||||||
self.is_flag = False
|
self.is_flag = False
|
||||||
|
self.skip_constructor_id = False
|
||||||
self.flag_index = -1
|
self.flag_index = -1
|
||||||
|
|
||||||
# Special case: some types can be inferred, which makes it
|
# Special case: some types can be inferred, which makes it
|
||||||
|
@ -222,7 +228,7 @@ class TLArg:
|
||||||
self.type = flag_match.group(2)
|
self.type = flag_match.group(2)
|
||||||
|
|
||||||
# Then check if the type is a Vector<REAL_TYPE>
|
# Then check if the type is a Vector<REAL_TYPE>
|
||||||
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:
|
if vector_match:
|
||||||
self.is_vector = True
|
self.is_vector = True
|
||||||
|
|
||||||
|
@ -234,6 +240,11 @@ class TLArg:
|
||||||
# Update the type to match the one inside the vector
|
# Update the type to match the one inside the vector
|
||||||
self.type = vector_match.group(1)
|
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",
|
# 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.
|
# 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
|
# Note that this is not a valid Telegram object, but it's easier to work with
|
||||||
|
|
|
@ -5,7 +5,7 @@ import struct
|
||||||
from zlib import crc32
|
from zlib import crc32
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from .parser import SourceBuilder, TLParser
|
from .parser import SourceBuilder, TLParser, TLObject
|
||||||
AUTO_GEN_NOTICE = \
|
AUTO_GEN_NOTICE = \
|
||||||
'"""File generated by TLObjects\' generator. All changes will be ERASED"""'
|
'"""File generated by TLObjects\' generator. All changes will be ERASED"""'
|
||||||
|
|
||||||
|
@ -129,6 +129,9 @@ class TLGenerator:
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
'from {}.tl.tlobject import TLObject'.format('.' * depth)
|
'from {}.tl.tlobject import TLObject'.format('.' * depth)
|
||||||
)
|
)
|
||||||
|
builder.writeln(
|
||||||
|
'from {}.tl import types'.format('.' * depth)
|
||||||
|
)
|
||||||
|
|
||||||
# Add the relative imports to the namespaces,
|
# Add the relative imports to the namespaces,
|
||||||
# unless we already are in a namespace.
|
# unless we already are in a namespace.
|
||||||
|
@ -151,7 +154,7 @@ class TLGenerator:
|
||||||
# for all those TLObjects with arg.can_be_inferred.
|
# for all those TLObjects with arg.can_be_inferred.
|
||||||
builder.writeln('import os')
|
builder.writeln('import os')
|
||||||
|
|
||||||
# Import struct for the .to_bytes(self) serialization
|
# Import struct for the .__bytes__(self) serialization
|
||||||
builder.writeln('import struct')
|
builder.writeln('import struct')
|
||||||
|
|
||||||
# Generate the class for every TLObject
|
# Generate the class for every TLObject
|
||||||
|
@ -299,8 +302,8 @@ class TLGenerator:
|
||||||
|
|
||||||
builder.end_block()
|
builder.end_block()
|
||||||
|
|
||||||
# Write the .to_bytes() function
|
# Write the .__bytes__() function
|
||||||
builder.writeln('def to_bytes(self):')
|
builder.writeln('def __bytes__(self):')
|
||||||
|
|
||||||
# Some objects require more than one flag parameter to be set
|
# Some objects require more than one flag parameter to be set
|
||||||
# at the same time. In this case, add an assertion.
|
# at the same time. In this case, add an assertion.
|
||||||
|
@ -311,11 +314,11 @@ class TLGenerator:
|
||||||
|
|
||||||
for ra in repeated_args.values():
|
for ra in repeated_args.values():
|
||||||
if len(ra) > 1:
|
if len(ra) > 1:
|
||||||
cnd1 = ('self.{} is None'.format(a.name) for a in ra)
|
cnd1 = ('self.{}'.format(a.name) for a in ra)
|
||||||
cnd2 = ('self.{} is not None'.format(a.name) for a in ra)
|
cnd2 = ('not self.{}'.format(a.name) for a in ra)
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
"assert ({}) or ({}), '{} parameters must all "
|
"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),
|
' and '.join(cnd1), ' and '.join(cnd2),
|
||||||
', '.join(a.name for a in ra)
|
', '.join(a.name for a in ra)
|
||||||
)
|
)
|
||||||
|
@ -438,10 +441,10 @@ class TLGenerator:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_to_bytes(builder, arg, args, name=None):
|
def write_to_bytes(builder, arg, args, name=None):
|
||||||
"""
|
"""
|
||||||
Writes the .to_bytes() code for the given argument
|
Writes the .__bytes__() code for the given argument
|
||||||
:param builder: The source code builder
|
:param builder: The source code builder
|
||||||
:param arg: The argument to write
|
:param arg: The argument to write
|
||||||
:param args: All the other arguments in TLObject same to_bytes.
|
:param args: All the other arguments in TLObject same __bytes__.
|
||||||
This is required to determine the flags value
|
This is required to determine the flags value
|
||||||
:param name: The name of the argument. Defaults to "self.argname"
|
:param name: The name of the argument. Defaults to "self.argname"
|
||||||
This argument is an option because it's required when
|
This argument is an option because it's required when
|
||||||
|
@ -537,7 +540,7 @@ class TLGenerator:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Else it may be a custom type
|
# Else it may be a custom type
|
||||||
builder.write('{}.to_bytes()'.format(name))
|
builder.write('bytes({})'.format(name))
|
||||||
|
|
||||||
if arg.is_flag:
|
if arg.is_flag:
|
||||||
builder.write(')')
|
builder.write(')')
|
||||||
|
@ -638,7 +641,11 @@ class TLGenerator:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Else it may be a custom type
|
# Else it may be a custom type
|
||||||
|
if not arg.skip_constructor_id:
|
||||||
builder.writeln('{} = reader.tgread_object()'.format(name))
|
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)
|
# End vector and flag blocks if required (if we opened them before)
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user