Make lint happier

This commit is contained in:
Lonami Exo 2017-09-04 17:10:04 +02:00
parent 7f700c3bc1
commit 97cab7347b
22 changed files with 210 additions and 117 deletions

View File

@ -8,9 +8,10 @@ class DocsWriter:
"""Initializes the writer to the specified output file, """Initializes the writer to the specified output file,
creating the parent directories when used if required. creating the parent directories when used if required.
'type_to_path_function' should be a function which, given a type name 'type_to_path_function' should be a function which, given a type
and a named argument relative_to, returns the file path for the specified name and a named argument relative_to, returns the file path for
type, relative to the given filename""" the specified type, relative to the given filename
"""
self.filename = filename self.filename = filename
self.handle = None self.handle = None
@ -18,7 +19,9 @@ class DocsWriter:
self.menu_separator_tag = None self.menu_separator_tag = None
# Utility functions TODO There must be a better way # Utility functions TODO There must be a better way
self.type_to_path = lambda t: type_to_path_function(t, relative_to=self.filename) self.type_to_path = lambda t: type_to_path_function(
t, relative_to=self.filename
)
# Control signals # Control signals
self.menu_began = False self.menu_began = False
@ -28,7 +31,9 @@ class DocsWriter:
# High level writing # High level writing
def write_head(self, title, relative_css_path): def write_head(self, title, relative_css_path):
"""Writes the head part for the generated document, with the given title and CSS""" """Writes the head part for the generated document,
with the given title and CSS
"""
self.write('''<!DOCTYPE html> self.write('''<!DOCTYPE html>
<html> <html>
<head> <head>
@ -50,9 +55,12 @@ class DocsWriter:
<div id="main_div">''') <div id="main_div">''')
def set_menu_separator(self, relative_image_path): def set_menu_separator(self, relative_image_path):
"""Sets the menu separator. Must be called before adding entries to the menu""" """Sets the menu separator.
Must be called before adding entries to the menu
"""
if relative_image_path: if relative_image_path:
self.menu_separator_tag = '<img src="%s" alt="/" />' % relative_image_path self.menu_separator_tag = \
'<img src="{}" alt="/" />'.format(relative_image_path)
else: else:
self.menu_separator_tag = None self.menu_separator_tag = None
@ -86,13 +94,17 @@ class DocsWriter:
self.write('</ul>') self.write('</ul>')
def write_title(self, title, level=1): def write_title(self, title, level=1):
"""Writes a title header in the document body, with an optional depth level""" """Writes a title header in the document body,
with an optional depth level
"""
self.write('<h%d>' % level) self.write('<h%d>' % level)
self.write(title) self.write(title)
self.write('</h%d>' % level) self.write('</h%d>' % level)
def write_code(self, tlobject): def write_code(self, tlobject):
"""Writes the code for the given 'tlobject' properly formatted ith with hyperlinks""" """Writes the code for the given 'tlobject' properly
formatted with hyperlinks
"""
self.write('<pre>---') self.write('<pre>---')
self.write('functions' if tlobject.is_function else 'types') self.write('functions' if tlobject.is_function else 'types')
self.write('---\n') self.write('---\n')
@ -127,7 +139,9 @@ class DocsWriter:
self.write('!') self.write('!')
if arg.is_vector: if arg.is_vector:
self.write('<a href="%s">Vector</a>&lt;' % self.type_to_path('vector')) self.write(
'<a href="%s">Vector</a>&lt;' % self.type_to_path('vector')
)
# Argument type # Argument type
if arg.type: if arg.type:
@ -146,7 +160,7 @@ class DocsWriter:
if arg.generic_definition: if arg.generic_definition:
self.write('}') self.write('}')
# Now write the resulting type (result from a function, or type for a constructor) # Now write the resulting type (result from a function/type)
self.write(' = ') self.write(' = ')
generic_name = next((arg.name for arg in tlobject.args generic_name = next((arg.name for arg in tlobject.args
if arg.generic_definition), None) if arg.generic_definition), None)

View File

@ -16,7 +16,7 @@ from telethon_generator.parser import TLParser, TLObject
# TLObject -> Python class name # TLObject -> Python class name
def get_class_name(tlobject): def get_class_name(tlobject):
"""Gets the class name following the Python style guidelines, in ThisClassFormat""" """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
name = tlobject.name if isinstance(tlobject, TLObject) else tlobject name = tlobject.name if isinstance(tlobject, TLObject) else tlobject
result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name) result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
@ -93,9 +93,9 @@ def get_path_for_type(type_, relative_to='.'):
elif '.' in type_: elif '.' in type_:
# If it's not a core type, then it has to be a custom Telegram type # If it's not a core type, then it has to be a custom Telegram type
namespace, name = type_.split('.') namespace, name = type_.split('.')
path = 'types/%s/%s' % (namespace, get_file_name(name, add_extension=True)) path = 'types/%s/%s' % (namespace, get_file_name(name, True))
else: else:
path = 'types/%s' % get_file_name(type_, add_extension=True) path = 'types/%s' % get_file_name(type_, True)
return get_relative_path(path, relative_to) return get_relative_path(path, relative_to)
@ -116,7 +116,7 @@ def get_relative_paths(original, relative_to):
# Generate a index.html file for the given folder # Generate a index.html file for the given folder
def find_title(html_file): def find_title(html_file):
"""Finds the <title> for the given HTML file, returns (Unknown) if not found""" """Finds the <title> for the given HTML file, or (Unknown)"""
with open(html_file) as handle: with open(html_file) as handle:
for line in handle: for line in handle:
if '<title>' in line: if '<title>' in line:
@ -196,8 +196,25 @@ def generate_index(folder, original_paths):
docs.end_body() docs.end_body()
def get_description(arg):
"""Generates a proper description for the given argument"""
desc = []
if arg.can_be_inferred:
desc.append('If left to None, it will be inferred automatically.')
if arg.is_vector:
desc.append('A list must be supplied for this argument.')
if arg.is_generic:
desc.append('A different Request must be supplied for this argument.')
if arg.is_flag:
desc.append('This argument can be omitted.')
return ' '.join(desc)
def generate_documentation(scheme_file): def generate_documentation(scheme_file):
"""Generates the documentation HTML files from from scheme.tl to /methods and /constructors, etc.""" """Generates the documentation HTML files from from scheme.tl to
/methods and /constructors, etc.
"""
original_paths = { original_paths = {
'css': 'css/docs.css', 'css': 'css/docs.css',
'arrow': 'img/arrow.svg', 'arrow': 'img/arrow.svg',
@ -234,7 +251,8 @@ def generate_documentation(scheme_file):
# Determine the relative paths for this file # Determine the relative paths for this file
paths = get_relative_paths(original_paths, relative_to=filename) paths = get_relative_paths(original_paths, relative_to=filename)
with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: with DocsWriter(filename, type_to_path_function=get_path_for_type) \
as docs:
docs.write_head( docs.write_head(
title=get_class_name(tlobject), title=get_class_name(tlobject),
relative_css_path=paths['css']) relative_css_path=paths['css'])
@ -274,8 +292,9 @@ def generate_documentation(scheme_file):
inner = tlobject.result inner = tlobject.result
docs.begin_table(column_count=1) docs.begin_table(column_count=1)
docs.add_row(inner, docs.add_row(inner, link=get_path_for_type(
link=get_path_for_type(inner, relative_to=filename)) inner, relative_to=filename
))
docs.end_table() docs.end_table()
constructors = tltypes.get(inner, []) constructors = tltypes.get(inner, [])
@ -294,9 +313,12 @@ def generate_documentation(scheme_file):
docs.end_table() docs.end_table()
# Return (or similar types) written. Now parameters/members # Return (or similar types) written. Now parameters/members
docs.write_title('Parameters' if tlobject.is_function else 'Members', level=3) docs.write_title(
'Parameters' if tlobject.is_function else 'Members', level=3
)
# Sort the arguments in the same way they're sorted on the generated code (flags go last) # Sort the arguments in the same way they're sorted
# on the generated code (flags go last)
args = [ args = [
a for a in tlobject.sorted_args() a for a in tlobject.sorted_args()
if not a.flag_indicator and not a.generic_definition if not a.flag_indicator and not a.generic_definition
@ -315,22 +337,13 @@ def generate_documentation(scheme_file):
if arg.is_generic: if arg.is_generic:
docs.add_row('!' + arg.type, align='center') docs.add_row('!' + arg.type, align='center')
else: else:
docs.add_row(arg.type, docs.add_row(
link=get_path_for_type(arg.type, relative_to=filename), arg.type, align='center', link=
align='center') get_path_for_type(arg.type, relative_to=filename)
)
# Create a description for this argument # Add a description for this argument
description = '' docs.add_row(get_description(arg))
if arg.can_be_inferred:
description += 'If left to None, it will be inferred automatically. '
if arg.is_vector:
description += 'A list must be supplied for this argument. '
if arg.is_generic:
description += 'A different MTProtoRequest must be supplied for this argument. '
if arg.is_flag:
description += 'This argument can be omitted. '
docs.add_row(description.strip())
docs.end_table() docs.end_table()
else: else:
@ -342,14 +355,14 @@ def generate_documentation(scheme_file):
docs.end_body() docs.end_body()
# Find all the available types (which are not the same as the constructors) # Find all the available types (which are not the same as the constructors)
# Each type has a list of constructors associated to it, so it should be a map # Each type has a list of constructors associated to it, hence is a map
print('Generating types documentation...') print('Generating types documentation...')
for tltype, constructors in tltypes.items(): for tltype, constructors in tltypes.items():
filename = get_path_for_type(tltype) filename = get_path_for_type(tltype)
out_dir = os.path.dirname(filename) out_dir = os.path.dirname(filename)
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# Since we don't have access to the full TLObject, split the type into namespace.name # Since we don't have access to the full TLObject, split the type
if '.' in tltype: if '.' in tltype:
namespace, name = tltype.split('.') namespace, name = tltype.split('.')
else: else:
@ -358,7 +371,8 @@ def generate_documentation(scheme_file):
# Determine the relative paths for this file # Determine the relative paths for this file
paths = get_relative_paths(original_paths, relative_to=out_dir) paths = get_relative_paths(original_paths, relative_to=out_dir)
with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: with DocsWriter(filename, type_to_path_function=get_path_for_type) \
as docs:
docs.write_head( docs.write_head(
title=get_class_name(name), title=get_class_name(name),
relative_css_path=paths['css']) relative_css_path=paths['css'])
@ -376,7 +390,8 @@ def generate_documentation(scheme_file):
elif len(constructors) == 1: elif len(constructors) == 1:
docs.write_text('This type has one constructor available.') docs.write_text('This type has one constructor available.')
else: else:
docs.write_text('This type has %d constructors available.' % len(constructors)) docs.write_text('This type has %d constructors available.' %
len(constructors))
docs.begin_table(2) docs.begin_table(2)
for constructor in constructors: for constructor in constructors:
@ -394,7 +409,10 @@ def generate_documentation(scheme_file):
elif len(functions) == 1: elif len(functions) == 1:
docs.write_text('Only the following method returns this type.') docs.write_text('Only the following method returns this type.')
else: else:
docs.write_text('The following %d methods return this type as a result.' % len(functions)) docs.write_text(
'The following %d methods return this type as a result.' %
len(functions)
)
docs.begin_table(2) docs.begin_table(2)
for func in functions: for func in functions:
@ -456,8 +474,8 @@ def generate_documentation(scheme_file):
docs.end_table() docs.end_table()
docs.end_body() docs.end_body()
# After everything's been written, generate an index.html file for every folder. # After everything's been written, generate an index.html per folder.
# This will be done automatically and not taking into account any additional # This will be done automatically and not taking into account any extra
# information that we have available, simply a file listing all the others # information that we have available, simply a file listing all the others
# accessible by clicking on their title # accessible by clicking on their title
print('Generating indices...') print('Generating indices...')
@ -485,13 +503,16 @@ def generate_documentation(scheme_file):
methods = sorted(methods, key=lambda m: m.name) methods = sorted(methods, key=lambda m: m.name)
constructors = sorted(constructors, key=lambda c: c.name) constructors = sorted(constructors, key=lambda c: c.name)
request_names = ', '.join('"' + get_class_name(m) + '"' for m in methods) def fmt(xs, formatter):
type_names = ', '.join('"' + get_class_name(t) + '"' for t in types) return ', '.join('"{}"'.format(formatter(x)) for x in xs)
constructor_names = ', '.join('"' + get_class_name(t) + '"' for t in constructors)
request_urls = ', '.join('"' + get_create_path_for(m) + '"' for m in methods) request_names = fmt(methods, get_class_name)
type_urls = ', '.join('"' + get_path_for_type(t) + '"' for t in types) type_names = fmt(types, get_class_name)
constructor_urls = ', '.join('"' + get_create_path_for(t) + '"' for t in constructors) constructor_names = fmt(constructors, get_class_name)
request_urls = fmt(methods, get_create_path_for)
type_urls = fmt(types, get_create_path_for)
constructor_urls = fmt(constructors, get_create_path_for)
replace_dict = { replace_dict = {
'type_count': len(types), 'type_count': len(types),

View File

@ -2,7 +2,9 @@
import unittest import unittest
if __name__ == '__main__': if __name__ == '__main__':
from telethon_tests import CryptoTests, ParserTests, TLTests, UtilsTests, NetworkTests from telethon_tests import \
CryptoTests, ParserTests, TLTests, UtilsTests, NetworkTests
test_classes = [CryptoTests, ParserTests, TLTests, UtilsTests] test_classes = [CryptoTests, ParserTests, TLTests, UtilsTests]
network = input('Run network tests (y/n)?: ').lower() == 'y' network = input('Run network tests (y/n)?: ').lower() == 'y'

View File

@ -14,7 +14,9 @@ class AuthKey:
self.key_id = reader.read_long(signed=False) self.key_id = reader.read_long(signed=False)
def calc_new_nonce_hash(self, new_nonce, number): def calc_new_nonce_hash(self, new_nonce, number):
"""Calculates the new nonce hash based on the current class fields' values""" """Calculates the new nonce hash based on
the current class fields' values
"""
with BinaryWriter() as writer: with BinaryWriter() as writer:
writer.write(new_nonce) writer.write(new_nonce)
writer.write_byte(number) writer.write_byte(number)

View File

@ -31,7 +31,8 @@ class CdnDecrypter:
# https://core.telegram.org/cdn # https://core.telegram.org/cdn
cdn_aes = AESModeCTR( cdn_aes = AESModeCTR(
key=cdn_redirect.encryption_key, key=cdn_redirect.encryption_key,
iv=cdn_redirect.encryption_iv[:12] + (offset >> 4).to_bytes(4, 'big') iv=
cdn_redirect.encryption_iv[:12] + (offset >> 4).to_bytes(4, 'big')
) )
# Create a new client on said CDN # Create a new client on said CDN

View File

@ -61,7 +61,9 @@ class Factorization:
@staticmethod @staticmethod
def factorize(pq): def factorize(pq):
"""Factorizes the given number and returns both the divisor and the number divided by the divisor""" """Factorizes the given number and returns both
the divisor and the number divided by the divisor
"""
if sympy: if sympy:
return tuple(sympy.ntheory.factorint(pq).keys()) return tuple(sympy.ntheory.factorint(pq).keys())
else: else:

View File

@ -10,7 +10,7 @@ from ..extensions import BinaryWriter
# {fingerprint: Crypto.PublicKey.RSA._RSAobj} dictionary # {fingerprint: Crypto.PublicKey.RSA._RSAobj} dictionary
_server_keys = { } _server_keys = {}
def get_byte_array(integer): def get_byte_array(integer):

View File

@ -30,7 +30,8 @@ class InvalidChecksumError(Exception):
def __init__(self, checksum, valid_checksum): def __init__(self, checksum, valid_checksum):
super().__init__( super().__init__(
self, self,
'Invalid checksum ({} when {} was expected). This packet should be skipped.' 'Invalid checksum ({} when {} was expected). '
'This packet should be skipped.'
.format(checksum, valid_checksum)) .format(checksum, valid_checksum))
self.checksum = checksum self.checksum = checksum

View File

@ -26,7 +26,8 @@ class BinaryReader:
# region Reading # region Reading
# "All numbers are written as little endian." |> Source: https://core.telegram.org/mtproto # "All numbers are written as little endian."
# https://core.telegram.org/mtproto
def read_byte(self): def read_byte(self):
"""Reads a single byte value""" """Reads a single byte value"""
return self.read(1)[0] return self.read(1)[0]
@ -56,8 +57,7 @@ class BinaryReader:
"""Read the given amount of bytes""" """Read the given amount of bytes"""
result = self.reader.read(length) result = self.reader.read(length)
if len(result) != length: if len(result) != length:
raise BufferError( raise BufferError('No more data left to read')
'Trying to read outside the data bounds (no more data left to read)')
return result return result
@ -70,7 +70,9 @@ class BinaryReader:
# region Telegram custom reading # region Telegram custom reading
def tgread_bytes(self): def tgread_bytes(self):
"""Reads a Telegram-encoded byte array, without the need of specifying its length""" """Reads a Telegram-encoded byte array,
without the need of specifying its length
"""
first_byte = self.read_byte() first_byte = self.read_byte()
if first_byte == 254: if first_byte == 254:
length = self.read_byte() | (self.read_byte() << 8) | ( length = self.read_byte() | (self.read_byte() << 8) | (
@ -102,7 +104,9 @@ class BinaryReader:
raise ValueError('Invalid boolean code {}'.format(hex(value))) raise ValueError('Invalid boolean code {}'.format(hex(value)))
def tgread_date(self): def tgread_date(self):
"""Reads and converts Unix time (used by Telegram) into a Python datetime object""" """Reads and converts Unix time (used by Telegram)
into a Python datetime object
"""
value = self.read_int() value = self.read_int()
return None if value == 0 else datetime.fromtimestamp(value) return None if value == 0 else datetime.fromtimestamp(value)
@ -152,7 +156,9 @@ class BinaryReader:
self.reader.seek(position) self.reader.seek(position)
def seek(self, offset): def seek(self, offset):
"""Seeks the stream position given an offset from the current position. May be negative""" """Seeks the stream position given an offset from the
current position. The offset may be negative
"""
self.reader.seek(offset, os.SEEK_CUR) self.reader.seek(offset, os.SEEK_CUR)
# endregion # endregion

View File

@ -22,21 +22,22 @@ class BinaryWriter:
# region Writing # region Writing
# "All numbers are written as little endian." |> Source: https://core.telegram.org/mtproto # "All numbers are written as little endian."
# https://core.telegram.org/mtproto
def write_byte(self, value): def write_byte(self, value):
"""Writes a single byte value""" """Writes a single byte value"""
self.writer.write(pack('B', value)) self.writer.write(pack('B', value))
self.written_count += 1 self.written_count += 1
def write_int(self, value, signed=True): def write_int(self, value, signed=True):
"""Writes an integer value (4 bytes), which can or cannot be signed""" """Writes an integer value (4 bytes), optionally signed"""
self.writer.write( self.writer.write(
int.to_bytes( int.to_bytes(
value, length=4, byteorder='little', signed=signed)) value, length=4, byteorder='little', signed=signed))
self.written_count += 4 self.written_count += 4
def write_long(self, value, signed=True): def write_long(self, value, signed=True):
"""Writes a long integer value (8 bytes), which can or cannot be signed""" """Writes a long integer value (8 bytes), optionally signed"""
self.writer.write( self.writer.write(
int.to_bytes( int.to_bytes(
value, length=8, byteorder='little', signed=signed)) value, length=8, byteorder='little', signed=signed))
@ -101,7 +102,9 @@ class BinaryWriter:
self.write_int(0x997275b5 if boolean else 0xbc799737, signed=False) self.write_int(0x997275b5 if boolean else 0xbc799737, signed=False)
def tgwrite_date(self, datetime): def tgwrite_date(self, datetime):
"""Converts a Python datetime object into Unix time (used by Telegram) and writes it""" """Converts a Python datetime object into Unix time
(used by Telegram) and writes it
"""
value = 0 if datetime is None else int(datetime.timestamp()) value = 0 if datetime is None else int(datetime.timestamp())
self.write_int(value) self.write_int(value)
@ -127,14 +130,18 @@ class BinaryWriter:
self.writer.close() self.writer.close()
def get_bytes(self, flush=True): def get_bytes(self, flush=True):
"""Get the current bytes array content from the buffer, optionally flushing first""" """Get the current bytes array content from the buffer,
optionally flushing first
"""
if flush: if flush:
self.writer.flush() self.writer.flush()
return self.writer.raw.getvalue() return self.writer.raw.getvalue()
def get_written_bytes_count(self): def get_written_bytes_count(self):
"""Gets the count of bytes written in the buffer. """Gets the count of bytes written in the buffer.
This may NOT be equal to the stream length if one was provided when initializing the writer""" This may NOT be equal to the stream length if one
was provided when initializing the writer
"""
return self.written_count return self.written_count
# with block # with block

View File

@ -22,7 +22,9 @@ def ensure_parent_dir_exists(file_path):
def calc_key(shared_key, msg_key, client): def calc_key(shared_key, msg_key, client):
"""Calculate the key based on Telegram guidelines, specifying whether it's the client or not""" """Calculate the key based on Telegram guidelines,
specifying whether it's the client or not
"""
x = 0 if client else 8 x = 0 if client else 8
sha1a = sha1(msg_key + shared_key[x:x + 32]).digest() sha1a = sha1(msg_key + shared_key[x:x + 32]).digest()
@ -56,7 +58,9 @@ def generate_key_data_from_nonce(server_nonce, new_nonce):
def get_password_hash(pw, current_salt): def get_password_hash(pw, current_salt):
"""Gets the password hash for the two-step verification. """Gets the password hash for the two-step verification.
current_salt should be the byte array provided by invoking GetPasswordRequest()""" current_salt should be the byte array provided by
invoking GetPasswordRequest()
"""
# Passwords are encoded as UTF-8 # Passwords are encoded as UTF-8
# At https://github.com/DrKLO/Telegram/blob/e31388 # At https://github.com/DrKLO/Telegram/blob/e31388

View File

@ -217,6 +217,8 @@ def do_authentication(connection):
def get_int(byte_array, signed=True): def get_int(byte_array, signed=True):
"""Gets the specified integer from its byte array. This should be used by the authenticator, """Gets the specified integer from its byte array.
who requires the data to be in big endian""" This should be used by the authenticator,
who requires the data to be in big endian
"""
return int.from_bytes(byte_array, byteorder='big', signed=signed) return int.from_bytes(byte_array, byteorder='big', signed=signed)

View File

@ -61,7 +61,8 @@ class Connection:
setattr(self, 'send', self._send_intermediate) setattr(self, 'send', self._send_intermediate)
setattr(self, 'recv', self._recv_intermediate) setattr(self, 'recv', self._recv_intermediate)
elif mode in (ConnectionMode.TCP_ABRIDGED, ConnectionMode.TCP_OBFUSCATED): elif mode in (ConnectionMode.TCP_ABRIDGED,
ConnectionMode.TCP_OBFUSCATED):
setattr(self, 'send', self._send_abridged) setattr(self, 'send', self._send_abridged)
setattr(self, 'recv', self._recv_abridged) setattr(self, 'recv', self._recv_abridged)

View File

@ -4,7 +4,9 @@ from ..extensions import BinaryReader, BinaryWriter
class MtProtoPlainSender: class MtProtoPlainSender:
"""MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)""" """MTProto Mobile Protocol plain sender
(https://core.telegram.org/mtproto/description#unencrypted-messages)
"""
def __init__(self, connection): def __init__(self, connection):
self._sequence = 0 self._sequence = 0
@ -19,7 +21,9 @@ class MtProtoPlainSender:
self._connection.close() self._connection.close()
def send(self, data): def send(self, data):
"""Sends a plain packet (auth_key_id = 0) containing the given message body (data)""" """Sends a plain packet (auth_key_id = 0) containing the
given message body (data)
"""
with BinaryWriter(known_length=len(data) + 20) as writer: with BinaryWriter(known_length=len(data) + 20) as writer:
writer.write_long(0) writer.write_long(0)
writer.write_long(self._get_new_msg_id()) writer.write_long(self._get_new_msg_id())

View File

@ -31,7 +31,7 @@ class MtProtoSender:
# Store an RLock instance to make this class safely multi-threaded # Store an RLock instance to make this class safely multi-threaded
self._lock = RLock() self._lock = RLock()
# Used when logging out, the only request that seems to use 'ack' requests # Used when logging out, the only request that seems to use 'ack'
# TODO There might be a better way to handle msgs_ack requests # TODO There might be a better way to handle msgs_ack requests
self.logging_out = False self.logging_out = False
@ -114,7 +114,10 @@ class MtProtoSender:
def _send_packet(self, packet, request): def _send_packet(self, packet, request):
"""Sends the given packet bytes with the additional """Sends the given packet bytes with the additional
information of the original request. This does NOT lock the threads!""" information of the original request.
This does NOT lock the threads!
"""
request.request_msg_id = self.session.get_new_msg_id() request.request_msg_id = self.session.get_new_msg_id()
# First calculate plain_text to encrypt it # First calculate plain_text to encrypt it
@ -183,7 +186,7 @@ class MtProtoSender:
reader.seek(-4) reader.seek(-4)
# The following codes are "parsed manually" # The following codes are "parsed manually"
if code == 0xf35c6d01: # rpc_result, (response of an RPC call, i.e., we sent a request) if code == 0xf35c6d01: # rpc_result, (response of an RPC call)
return self._handle_rpc_result(msg_id, sequence, reader) return self._handle_rpc_result(msg_id, sequence, reader)
if code == 0x347773c5: # pong if code == 0x347773c5: # pong
@ -219,11 +222,15 @@ class MtProtoSender:
if code in tlobjects: if code in tlobjects:
result = reader.tgread_object() result = reader.tgread_object()
if self.unhandled_callbacks: if self.unhandled_callbacks:
self._logger.debug('Passing TLObject to callbacks %s', repr(result)) self._logger.debug(
'Passing TLObject to callbacks %s', repr(result)
)
for callback in self.unhandled_callbacks: for callback in self.unhandled_callbacks:
callback(result) callback(result)
else: else:
self._logger.debug('Ignoring unhandled TLObject %s', repr(result)) self._logger.debug(
'Ignoring unhandled TLObject %s', repr(result)
)
return True return True

View File

@ -184,8 +184,9 @@ class TelegramBareClient:
except (RPCError, ConnectionError) as error: except (RPCError, ConnectionError) as error:
# Probably errors from the previous session, ignore them # Probably errors from the previous session, ignore them
self.disconnect() self.disconnect()
self._logger.debug('Could not stabilise initial connection: {}' self._logger.debug(
.format(error)) 'Could not stabilise initial connection: {}'.format(error)
)
return None if initial_query else False return None if initial_query else False
def disconnect(self): def disconnect(self):
@ -515,8 +516,10 @@ class TelegramBareClient:
progress_callback(f.tell(), file_size) progress_callback(f.tell(), file_size)
finally: finally:
if cdn_decrypter: if cdn_decrypter:
try: cdn_decrypter.client.disconnect() try:
except: pass cdn_decrypter.client.disconnect()
except:
pass
if isinstance(file, str): if isinstance(file, str):
f.close() f.close()

View File

@ -124,7 +124,6 @@ class TelegramClient(TelegramBareClient):
# Constantly read for results and updates from within the main client # Constantly read for results and updates from within the main client
self._recv_thread = None self._recv_thread = None
# endregion # endregion
# region Connecting # region Connecting

View File

@ -73,7 +73,7 @@ class Session:
'time_offset': self.time_offset, 'time_offset': self.time_offset,
'server_address': self.server_address, 'server_address': self.server_address,
'auth_key_data': 'auth_key_data':
b64encode(self.auth_key.key).decode('ascii')\ b64encode(self.auth_key.key).decode('ascii')
if self.auth_key else None if self.auth_key else None
}, file) }, file)

View File

@ -11,21 +11,27 @@ class SourceBuilder:
self.auto_added_line = False self.auto_added_line = False
def indent(self): def indent(self):
"""Indents the current source code line by the current indentation level""" """Indents the current source code line
by the current indentation level
"""
self.write(' ' * (self.current_indent * self.indent_size)) self.write(' ' * (self.current_indent * self.indent_size))
def write(self, string): def write(self, string):
"""Writes a string into the source code, applying indentation if required""" """Writes a string into the source code,
applying indentation if required
"""
if self.on_new_line: if self.on_new_line:
self.on_new_line = False # We're not on a new line anymore self.on_new_line = False # We're not on a new line anymore
if string.strip( # If the string was not empty, indent; Else probably a new line
): # If the string was not empty, indent; Else it probably was a new line if string.strip():
self.indent() self.indent()
self.out_stream.write(string) self.out_stream.write(string)
def writeln(self, string=''): def writeln(self, string=''):
"""Writes a string into the source code _and_ appends a new line, applying indentation if required""" """Writes a string into the source code _and_ appends a new line,
applying indentation if required
"""
self.write(string + '\n') self.write(string + '\n')
self.on_new_line = True self.on_new_line = True

View File

@ -11,7 +11,8 @@ class TLParser:
"""This method yields TLObjects from a given .tl file""" """This method yields TLObjects from a given .tl file"""
with open(file_path, encoding='utf-8') as file: with open(file_path, encoding='utf-8') as file:
# Start by assuming that the next found line won't be a function (and will hence be a type) # Start by assuming that the next found line won't
# be a function (and will hence be a type)
is_function = False is_function = False
# Read all the lines from the .tl file # Read all the lines from the .tl file
@ -21,7 +22,8 @@ class TLParser:
# Ensure that the line is not a comment # Ensure that the line is not a comment
if line and not line.startswith('//'): if line and not line.startswith('//'):
# Check whether the line is a type change (types ⋄ functions) or not # Check whether the line is a type change
# (types <-> functions) or not
match = re.match('---(\w+)---', line) match = re.match('---(\w+)---', line)
if match: if match:
following_types = match.group(1) following_types = match.group(1)

View File

@ -405,15 +405,14 @@ class TLGenerator:
@staticmethod @staticmethod
def get_class_name(tlobject): def get_class_name(tlobject):
"""Gets the class name following the Python style guidelines, in ThisClassFormat""" """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
# Also, '_' could be replaced for ' ', then use .title(), and then remove ' '
result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(),
tlobject.name) tlobject.name)
result = result[:1].upper() + result[1:].replace( result = result[:1].upper() + result[1:].replace(
'_', '') # Replace again to fully ensure! '_', '') # Replace again to fully ensure!
# If it's a function, let it end with "Request" to identify them more easily # If it's a function, let it end with "Request" to identify them
if tlobject.is_function: if tlobject.is_function:
result += 'Request' result += 'Request'
return result return result
@ -436,9 +435,11 @@ class TLGenerator:
Writes the write code for the given argument Writes the write 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 on_send. This is required to determine the flags value :param args: All the other arguments in TLObject same on_send.
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 writing Vectors<> This argument is an option because it's required when
writing Vectors<>
""" """
if arg.generic_definition: if arg.generic_definition:
@ -447,8 +448,10 @@ class TLGenerator:
if name is None: if name is None:
name = 'self.{}'.format(arg.name) name = 'self.{}'.format(arg.name)
# The argument may be a flag, only write if it's not None AND if it's not a True type # The argument may be a flag, only write if it's not None AND
# True types are not actually sent, but instead only used to determine the flags # if it's not a True type.
# True types are not actually sent, but instead only used to
# determine the flags.
if arg.is_flag: if arg.is_flag:
if arg.type == 'true': if arg.type == 'true':
return # Exit, since True type is never written return # Exit, since True type is never written
@ -457,8 +460,7 @@ class TLGenerator:
if arg.is_vector: if arg.is_vector:
if arg.use_vector_id: if arg.use_vector_id:
builder.writeln( builder.writeln('writer.write_int(0x1cb5c415, signed=False)')
"writer.write_int(0x1cb5c415, signed=False) # Vector's constructor ID")
builder.writeln('writer.write_int(len({}))'.format(name)) builder.writeln('writer.write_int(len({}))'.format(name))
builder.writeln('for _x in {}:'.format(name)) builder.writeln('for _x in {}:'.format(name))
@ -501,7 +503,7 @@ class TLGenerator:
elif 'Bool' == arg.type: elif 'Bool' == arg.type:
builder.writeln('writer.tgwrite_bool({})'.format(name)) builder.writeln('writer.tgwrite_bool({})'.format(name))
elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags elif 'true' == arg.type:
pass # These are actually NOT written! Only used for flags pass # These are actually NOT written! Only used for flags
elif 'bytes' == arg.type: elif 'bytes' == arg.type:
@ -528,9 +530,11 @@ class TLGenerator:
: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 on_send. This is required to determine the flags value :param args: All the other arguments in TLObject same on_send.
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 writing Vectors<> This argument is an option because it's required when
writing Vectors<>
""" """
if arg.generic_definition: if arg.generic_definition:
@ -544,8 +548,10 @@ class TLGenerator:
if arg.is_flag: if arg.is_flag:
was_flag = True was_flag = True
builder.writeln('if (flags & (1 << {})) != 0:'.format( builder.writeln('if (flags & (1 << {})) != 0:'.format(
arg.flag_index)) arg.flag_index
# Temporary disable .is_flag not to enter this if again when calling the method recursively ))
# Temporary disable .is_flag not to enter this if
# again when calling the method recursively
arg.is_flag = False arg.is_flag = False
if arg.is_vector: if arg.is_vector:
@ -621,7 +627,8 @@ class TLGenerator:
Writes the receive code for the given function Writes the receive code for the given function
:param builder: The source code builder :param builder: The source code builder
:param tlobject: The TLObject for which the 'self.result = ' will be written :param tlobject: The TLObject for which the 'self.result = '
will be written
""" """
if tlobject.result.startswith('Vector<'): if tlobject.result.startswith('Vector<'):
# Vector results are a bit special since they can also be composed # Vector results are a bit special since they can also be composed
@ -631,13 +638,15 @@ class TLGenerator:
if tlobject.result == 'Vector<int>': if tlobject.result == 'Vector<int>':
builder.writeln('reader.read_int() # Vector id') builder.writeln('reader.read_int() # Vector id')
builder.writeln('count = reader.read_int()') builder.writeln('count = reader.read_int()')
builder.writeln('self.result = [reader.read_int() for _ in range(count)]') builder.writeln(
'self.result = [reader.read_int() for _ in range(count)]'
)
elif tlobject.result == 'Vector<long>': elif tlobject.result == 'Vector<long>':
builder.writeln('reader.read_int() # Vector id') builder.writeln('reader.read_int() # Vector id')
builder.writeln('count = reader.read_long()') builder.writeln('count = reader.read_long()')
builder.writeln('self.result = [reader.read_long() for _ in range(count)]') builder.writeln(
'self.result = [reader.read_long() for _ in range(count)]'
)
else: else:
builder.writeln('self.result = reader.tgread_vector()') builder.writeln('self.result = reader.tgread_vector()')
else: else:

View File

@ -13,8 +13,8 @@ api_hash = None
if not api_id or not api_hash: if not api_id or not api_hash:
raise ValueError('Please fill in both your api_id and api_hash.') raise ValueError('Please fill in both your api_id and api_hash.')
class HigherLevelTests(unittest.TestCase):
class HigherLevelTests(unittest.TestCase):
@staticmethod @staticmethod
def test_cdn_download(): def test_cdn_download():
client = TelegramClient(None, api_id, api_hash) client = TelegramClient(None, api_id, api_hash)
@ -24,7 +24,7 @@ class HigherLevelTests(unittest.TestCase):
try: try:
phone = '+999662' + str(randint(0, 9999)).zfill(4) phone = '+999662' + str(randint(0, 9999)).zfill(4)
client.send_code_request(phone) client.send_code_request(phone)
client.sign_up(phone, '22222', 'Test', 'DC') client.sign_up('22222', 'Test', 'DC')
me = client.get_me() me = client.get_me()
data = os.urandom(2 ** 17) data = os.urandom(2 ** 17)