mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-10 19:46:36 +03:00
Make lint happier
This commit is contained in:
parent
7f700c3bc1
commit
97cab7347b
|
@ -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><' % self.type_to_path('vector'))
|
self.write(
|
||||||
|
'<a href="%s">Vector</a><' % 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)
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user