From 51a531225fa36493d25cbe8a654ef68e63d53d63 Mon Sep 17 00:00:00 2001 From: Lonami Date: Sat, 17 Sep 2016 20:42:34 +0200 Subject: [PATCH] Totally refactored source files location Now it *should* be easier to turn Telethon into a pip package --- .gitignore | 6 +- README.md | 6 +- unit_tests.py => run_tests.py | 3 +- telethon/__init__.py | 3 + {crypto => telethon/crypto}/__init__.py | 0 {crypto => telethon/crypto}/aes.py | 0 {crypto => telethon/crypto}/auth_key.py | 4 +- {crypto => telethon/crypto}/factorizator.py | 0 {crypto => telethon/crypto}/rsa.py | 4 +- errors.py => telethon/errors.py | 6 - {utils => telethon}/helpers.py | 31 +- .../interactive_telegram_client.py | 41 +- {network => telethon/network}/__init__.py | 0 .../network}/authenticator.py | 8 +- .../network}/mtproto_plain_sender.py | 2 +- .../network}/mtproto_sender.py | 12 +- {network => telethon/network}/tcp_client.py | 4 +- .../network}/tcp_transport.py | 6 +- telethon/parser/__init__.py | 1 + .../parser}/markdown_parser.py | 2 +- .../telegram_client.py | 34 +- telethon/tl/__init__.py | 2 + {tl => telethon/tl}/mtproto_request.py | 0 {tl => telethon/tl}/session.py | 2 +- {utils => telethon/utils}/__init__.py | 3 +- {utils => telethon/utils}/binary_reader.py | 4 +- {utils => telethon/utils}/binary_writer.py | 0 .../parser}/__init__.py | 2 +- .../parser}/source_builder.py | 0 .../parser/tl_object.py | 0 .../parser}/tl_parser.py | 2 +- scheme.tl => telethon_generator/scheme.tl | 0 telethon_generator/tl_generator.py | 401 ++++++++++++++++++ {unittests => telethon_tests}/__init__.py | 0 {unittests => telethon_tests}/crypto_tests.py | 12 +- .../network_tests.py | 6 +- {unittests => telethon_tests}/parser_tests.py | 0 {unittests => telethon_tests}/tl_tests.py | 0 {unittests => telethon_tests}/utils_tests.py | 2 +- tl/__init__.py | 4 - tl_generator.py | 393 ----------------- try_telethon.py | 43 ++ 42 files changed, 518 insertions(+), 531 deletions(-) rename unit_tests.py => run_tests.py (85%) create mode 100644 telethon/__init__.py rename {crypto => telethon/crypto}/__init__.py (100%) rename {crypto => telethon/crypto}/aes.py (100%) rename {crypto => telethon/crypto}/auth_key.py (88%) rename {crypto => telethon/crypto}/factorizator.py (100%) rename {crypto => telethon/crypto}/rsa.py (97%) rename errors.py => telethon/errors.py (97%) rename {utils => telethon}/helpers.py (64%) rename interactive_telegram_client.py => telethon/interactive_telegram_client.py (89%) rename {network => telethon/network}/__init__.py (100%) rename {network => telethon/network}/authenticator.py (97%) rename {network => telethon/network}/mtproto_plain_sender.py (97%) rename {network => telethon/network}/mtproto_sender.py (98%) rename {network => telethon/network}/tcp_client.py (96%) rename {network => telethon/network}/tcp_transport.py (95%) create mode 100644 telethon/parser/__init__.py rename {parser => telethon/parser}/markdown_parser.py (98%) rename telegram_client.py => telethon/telegram_client.py (96%) create mode 100755 telethon/tl/__init__.py rename {tl => telethon/tl}/mtproto_request.py (100%) rename {tl => telethon/tl}/session.py (98%) rename {utils => telethon/utils}/__init__.py (77%) rename {utils => telethon/utils}/binary_reader.py (98%) rename {utils => telethon/utils}/binary_writer.py (100%) rename {parser => telethon_generator/parser}/__init__.py (69%) rename {parser => telethon_generator/parser}/source_builder.py (100%) rename parser/tlobject.py => telethon_generator/parser/tl_object.py (100%) rename {parser => telethon_generator/parser}/tl_parser.py (96%) rename scheme.tl => telethon_generator/scheme.tl (100%) create mode 100755 telethon_generator/tl_generator.py rename {unittests => telethon_tests}/__init__.py (100%) rename {unittests => telethon_tests}/crypto_tests.py (93%) rename {unittests => telethon_tests}/network_tests.py (86%) rename {unittests => telethon_tests}/parser_tests.py (100%) rename {unittests => telethon_tests}/tl_tests.py (100%) rename {unittests => telethon_tests}/utils_tests.py (98%) delete mode 100755 tl/__init__.py delete mode 100755 tl_generator.py create mode 100644 try_telethon.py diff --git a/.gitignore b/.gitignore index 2a8a7662..f737e858 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .idea -tl/functions/ -tl/types/ -tl/all_tlobjects.py +telethon/tl/functions/ +telethon/tl/types/ +telethon/tl/all_tlobjects.py # User session *.session diff --git a/README.md b/README.md index 0349c172..64c45fda 100755 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ head to `api/` directory and create a copy of the `settings_example` file, namin Then fill the file with the corresponding values (your `api_id`, `api_hash` and phone number in international format). ## Running Telethon -First of all, you need to run the `tl_generator.py` by issuing `python3 tl_generator.py`. This will generate all the -TLObjects from the given `scheme.tl` file. When it's done, you can run `python3 interactive_telegram_client.py` to -start the interactive example. +First of all, you need to run the `tl_generator.py` (located under `telethon-generator/`) by issuing +`python3 tl_generator.py`. This will generate all the TLObjects from the given `scheme.tl` file. +When it's done, you can run `python3 try_telethon.py` to start the interactive example. ## Advanced uses ### Using more than just `TelegramClient` diff --git a/unit_tests.py b/run_tests.py similarity index 85% rename from unit_tests.py rename to run_tests.py index 17937052..0cdb8723 100644 --- a/unit_tests.py +++ b/run_tests.py @@ -2,8 +2,7 @@ import unittest if __name__ == '__main__': - - from unittests import CryptoTests, ParserTests, TLTests, UtilsTests, NetworkTests + from telethon_tests import CryptoTests, ParserTests, TLTests, UtilsTests, NetworkTests test_classes = [CryptoTests, ParserTests, TLTests, UtilsTests] network = input('Run network tests (y/n)?: ').lower() == 'y' diff --git a/telethon/__init__.py b/telethon/__init__.py new file mode 100644 index 00000000..e16e51ab --- /dev/null +++ b/telethon/__init__.py @@ -0,0 +1,3 @@ +from .errors import * +from .telegram_client import TelegramClient +from .interactive_telegram_client import InteractiveTelegramClient diff --git a/crypto/__init__.py b/telethon/crypto/__init__.py similarity index 100% rename from crypto/__init__.py rename to telethon/crypto/__init__.py diff --git a/crypto/aes.py b/telethon/crypto/aes.py similarity index 100% rename from crypto/aes.py rename to telethon/crypto/aes.py diff --git a/crypto/auth_key.py b/telethon/crypto/auth_key.py similarity index 88% rename from crypto/auth_key.py rename to telethon/crypto/auth_key.py index ecd85f57..356add55 100755 --- a/crypto/auth_key.py +++ b/telethon/crypto/auth_key.py @@ -1,5 +1,5 @@ -from utils import BinaryWriter, BinaryReader -import utils +from telethon.utils import BinaryWriter, BinaryReader +import telethon.helpers as utils class AuthKey: diff --git a/crypto/factorizator.py b/telethon/crypto/factorizator.py similarity index 100% rename from crypto/factorizator.py rename to telethon/crypto/factorizator.py diff --git a/crypto/rsa.py b/telethon/crypto/rsa.py similarity index 97% rename from crypto/rsa.py rename to telethon/crypto/rsa.py index 5faa764a..c71e914f 100755 --- a/crypto/rsa.py +++ b/telethon/crypto/rsa.py @@ -1,5 +1,5 @@ -import utils -from utils import BinaryWriter +from telethon.utils import BinaryWriter +import telethon.helpers as utils import os diff --git a/errors.py b/telethon/errors.py similarity index 97% rename from errors.py rename to telethon/errors.py index e2197542..90ba87c1 100644 --- a/errors.py +++ b/telethon/errors.py @@ -7,12 +7,6 @@ class ReadCancelledError(Exception): super().__init__(self, 'You must run `python3 tl_generator.py` first. #ReadTheDocs!') -class TLGeneratorNotRan(Exception): - """Occurs when you should've ran `tl_generator.py`, but you haven't""" - def __init__(self): - super().__init__(self, 'You must run `python3 tl_generator.py` first. #ReadTheDocs!') - - class InvalidParameterError(Exception): """Occurs when an invalid parameter is given, for example, when either A or B are required but none is given""" diff --git a/utils/helpers.py b/telethon/helpers.py similarity index 64% rename from utils/helpers.py rename to telethon/helpers.py index 97ee8c0d..eaee4fbe 100755 --- a/utils/helpers.py +++ b/telethon/helpers.py @@ -1,6 +1,4 @@ import os -import shutil -from utils import BinaryWriter import hashlib # region Multiple utilities @@ -11,22 +9,6 @@ def generate_random_long(signed=True): return int.from_bytes(os.urandom(8), signed=signed, byteorder='little') -def load_settings(path='api/settings'): - """Loads the user settings located under `api/`""" - settings = {} - with open(path, 'r', encoding='utf-8') as file: - for line in file: - value_pair = line.split('=') - left = value_pair[0].strip() - right = value_pair[1].strip() - if right.isnumeric(): - settings[left] = int(right) - else: - settings[left] = right - - return settings - - def ensure_parent_dir_exists(file_path): """Ensures that the parent directory exists""" parent = os.path.dirname(file_path) @@ -65,16 +47,9 @@ def generate_key_data_from_nonces(server_nonce, new_nonce): hash2 = sha1(bytes(server_nonce + new_nonce)) hash3 = sha1(bytes(new_nonce + new_nonce)) - with BinaryWriter() as key_buffer: - with BinaryWriter() as iv_buffer: - key_buffer.write(hash1) - key_buffer.write(hash2[:12]) - - iv_buffer.write(hash2[12:20]) - iv_buffer.write(hash3) - iv_buffer.write(new_nonce[:4]) - - return key_buffer.get_bytes(), iv_buffer.get_bytes() + key = hash1 + hash2[:12] + iv = hash2[12:20] + hash3 + new_nonce[:4] + return key, iv def sha1(data): diff --git a/interactive_telegram_client.py b/telethon/interactive_telegram_client.py similarity index 89% rename from interactive_telegram_client.py rename to telethon/interactive_telegram_client.py index 2d281314..8097b348 100644 --- a/interactive_telegram_client.py +++ b/telethon/interactive_telegram_client.py @@ -1,18 +1,7 @@ -import tl_generator -from tl.types import UpdateShortChatMessage -from tl.types import UpdateShortMessage - -if not tl_generator.tlobjects_exist(): - import errors - - raise errors.TLGeneratorNotRan() -else: - del tl_generator - -from telegram_client import TelegramClient -from utils.helpers import load_settings +from telethon.tl.types import UpdateShortChatMessage +from telethon.tl.types import UpdateShortMessage +from telethon import TelegramClient import shutil -import traceback # Get the (current) number of lines in the terminal cols, rows = shutil.get_terminal_size() @@ -226,27 +215,3 @@ class InteractiveTelegramClient(TelegramClient): elif type(update_object) is UpdateShortChatMessage: print('[Chat #{} sent {}]'.format(update_object.chat_id, update_object.message)) - - -if __name__ == '__main__': - # Load the settings and initialize the client - settings = load_settings() - client = InteractiveTelegramClient( - session_user_id=settings.get('session_name', 'anonymous'), - user_phone=str(settings['user_phone']), - layer=55, - api_id=settings['api_id'], - api_hash=settings['api_hash']) - - print('Initialization done!') - - try: - client.run() - - except Exception as e: - print('Unexpected error ({}): {} at\n{}'.format(type(e), e, traceback.format_exc())) - - finally: - print_title('Exit') - print('Thanks for trying the interactive example! Exiting...') - client.disconnect() diff --git a/network/__init__.py b/telethon/network/__init__.py similarity index 100% rename from network/__init__.py rename to telethon/network/__init__.py diff --git a/network/authenticator.py b/telethon/network/authenticator.py similarity index 97% rename from network/authenticator.py rename to telethon/network/authenticator.py index fdd0be65..49584331 100755 --- a/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -1,9 +1,9 @@ import os import time -import utils -from utils import BinaryWriter, BinaryReader -from crypto import AES, AuthKey, Factorizator, RSA -from network import MtProtoPlainSender +import telethon.helpers as utils +from telethon.utils import BinaryWriter, BinaryReader +from telethon.crypto import AES, AuthKey, Factorizator, RSA +from telethon.network import MtProtoPlainSender def do_authentication(transport): diff --git a/network/mtproto_plain_sender.py b/telethon/network/mtproto_plain_sender.py similarity index 97% rename from network/mtproto_plain_sender.py rename to telethon/network/mtproto_plain_sender.py index 3994f37a..c7cb581d 100755 --- a/network/mtproto_plain_sender.py +++ b/telethon/network/mtproto_plain_sender.py @@ -1,6 +1,6 @@ import time import random -from utils import BinaryWriter, BinaryReader +from telethon.utils import BinaryWriter, BinaryReader class MtProtoPlainSender: diff --git a/network/mtproto_sender.py b/telethon/network/mtproto_sender.py similarity index 98% rename from network/mtproto_sender.py rename to telethon/network/mtproto_sender.py index f49b6b84..023a7734 100755 --- a/network/mtproto_sender.py +++ b/telethon/network/mtproto_sender.py @@ -1,13 +1,13 @@ import gzip -from errors import * +from telethon.errors import * from time import sleep from threading import Thread, Lock -import utils -from crypto import AES -from utils import BinaryWriter, BinaryReader -from tl.types import MsgsAck -from tl.all_tlobjects import tlobjects +import telethon.helpers as utils +from telethon.crypto import AES +from telethon.utils import BinaryWriter, BinaryReader +from telethon.tl.types import MsgsAck +from telethon.tl.all_tlobjects import tlobjects class MtProtoSender: diff --git a/network/tcp_client.py b/telethon/network/tcp_client.py similarity index 96% rename from network/tcp_client.py rename to telethon/network/tcp_client.py index a098a567..2a7a0491 100755 --- a/network/tcp_client.py +++ b/telethon/network/tcp_client.py @@ -3,8 +3,8 @@ import socket import time from threading import Lock -from errors import ReadCancelledError -from utils import BinaryWriter +from telethon.errors import ReadCancelledError +from telethon.utils import BinaryWriter class TcpClient: diff --git a/network/tcp_transport.py b/telethon/network/tcp_transport.py similarity index 95% rename from network/tcp_transport.py rename to telethon/network/tcp_transport.py index 6268e191..894a08a6 100755 --- a/network/tcp_transport.py +++ b/telethon/network/tcp_transport.py @@ -1,7 +1,7 @@ -from network import TcpClient from binascii import crc32 -from errors import * -from utils import BinaryWriter +from telethon.network import TcpClient +from telethon.errors import * +from telethon.utils import BinaryWriter class TcpTransport: diff --git a/telethon/parser/__init__.py b/telethon/parser/__init__.py new file mode 100644 index 00000000..013c12b9 --- /dev/null +++ b/telethon/parser/__init__.py @@ -0,0 +1 @@ +from .markdown_parser import parse_message_entities diff --git a/parser/markdown_parser.py b/telethon/parser/markdown_parser.py similarity index 98% rename from parser/markdown_parser.py rename to telethon/parser/markdown_parser.py index e1e75ff4..40b6cc07 100644 --- a/parser/markdown_parser.py +++ b/telethon/parser/markdown_parser.py @@ -1,4 +1,4 @@ -from tl.types import MessageEntityBold, MessageEntityItalic, MessageEntityCode, MessageEntityTextUrl +from telethon.tl.types import MessageEntityBold, MessageEntityItalic, MessageEntityCode, MessageEntityTextUrl def parse_message_entities(msg): diff --git a/telegram_client.py b/telethon/telegram_client.py similarity index 96% rename from telegram_client.py rename to telethon/telegram_client.py index 62ca0f2f..c5e2b81d 100644 --- a/telegram_client.py +++ b/telethon/telegram_client.py @@ -4,31 +4,31 @@ from hashlib import md5 from os import path from mimetypes import guess_extension, guess_type -import utils -import network.authenticator - -from errors import * -from network import MtProtoSender, TcpTransport -from parser.markdown_parser import parse_message_entities - # For sending and receiving requests -from tl import MTProtoRequest -from tl import Session +from telethon.tl import MTProtoRequest +from telethon.tl import Session # The Requests and types that we'll be using -from tl.functions.upload import SaveBigFilePartRequest -from tl.types import \ +from telethon.tl.functions.upload import SaveBigFilePartRequest +from telethon.tl.types import \ PeerUser, PeerChat, PeerChannel, \ InputPeerUser, InputPeerChat, InputPeerChannel, InputPeerEmpty, \ InputFile, InputFileLocation, InputMediaUploadedPhoto, InputMediaUploadedDocument, \ MessageMediaContact, MessageMediaDocument, MessageMediaPhoto, \ DocumentAttributeAudio, DocumentAttributeFilename, InputDocumentFileLocation -from tl.functions import InvokeWithLayerRequest, InitConnectionRequest -from tl.functions.help import GetConfigRequest -from tl.functions.auth import SendCodeRequest, SignInRequest, SignUpRequest, LogOutRequest -from tl.functions.upload import SaveFilePartRequest, GetFileRequest -from tl.functions.messages import GetDialogsRequest, GetHistoryRequest, SendMessageRequest, SendMediaRequest +from telethon.tl.functions import InvokeWithLayerRequest, InitConnectionRequest +from telethon.tl.functions.help import GetConfigRequest +from telethon.tl.functions.auth import SendCodeRequest, SignInRequest, SignUpRequest, LogOutRequest +from telethon.tl.functions.upload import SaveFilePartRequest, GetFileRequest +from telethon.tl.functions.messages import GetDialogsRequest, GetHistoryRequest, SendMessageRequest, SendMediaRequest + +import telethon.helpers as utils +import telethon.network.authenticator as authenticator + +from telethon.errors import * +from telethon.network import MtProtoSender, TcpTransport +from telethon.parser.markdown_parser import parse_message_entities class TelegramClient: @@ -63,7 +63,7 @@ class TelegramClient: try: if not self.session.auth_key or reconnect: self.session.auth_key, self.session.time_offset = \ - network.authenticator.do_authentication(self.transport) + authenticator.do_authentication(self.transport) self.session.save() diff --git a/telethon/tl/__init__.py b/telethon/tl/__init__.py new file mode 100755 index 00000000..47104db9 --- /dev/null +++ b/telethon/tl/__init__.py @@ -0,0 +1,2 @@ +from telethon.tl.mtproto_request import MTProtoRequest +from telethon.tl.session import Session diff --git a/tl/mtproto_request.py b/telethon/tl/mtproto_request.py similarity index 100% rename from tl/mtproto_request.py rename to telethon/tl/mtproto_request.py diff --git a/tl/session.py b/telethon/tl/session.py similarity index 98% rename from tl/session.py rename to telethon/tl/session.py index ae61b028..088b224a 100755 --- a/tl/session.py +++ b/telethon/tl/session.py @@ -2,8 +2,8 @@ from os.path import isfile as file_exists import os import time import pickle -import utils import random +import telethon.helpers as utils class Session: diff --git a/utils/__init__.py b/telethon/utils/__init__.py similarity index 77% rename from utils/__init__.py rename to telethon/utils/__init__.py index f51ccc41..4923827f 100755 --- a/utils/__init__.py +++ b/telethon/utils/__init__.py @@ -1,3 +1,2 @@ -from .binary_reader import BinaryReader from .binary_writer import BinaryWriter -from .helpers import * +from .binary_reader import BinaryReader diff --git a/utils/binary_reader.py b/telethon/utils/binary_reader.py similarity index 98% rename from utils/binary_reader.py rename to telethon/utils/binary_reader.py index 86270941..50fd2359 100755 --- a/utils/binary_reader.py +++ b/telethon/utils/binary_reader.py @@ -1,8 +1,8 @@ from datetime import datetime from io import BytesIO, BufferedReader -from tl.all_tlobjects import tlobjects +from telethon.tl.all_tlobjects import tlobjects from struct import unpack -from errors import * +from telethon.errors import * import inspect import os diff --git a/utils/binary_writer.py b/telethon/utils/binary_writer.py similarity index 100% rename from utils/binary_writer.py rename to telethon/utils/binary_writer.py diff --git a/parser/__init__.py b/telethon_generator/parser/__init__.py similarity index 69% rename from parser/__init__.py rename to telethon_generator/parser/__init__.py index 6fa3c3ac..6f1a2a9d 100755 --- a/parser/__init__.py +++ b/telethon_generator/parser/__init__.py @@ -1,3 +1,3 @@ from .source_builder import SourceBuilder from .tl_parser import TLParser -from .tlobject import TLObject +from .tl_object import TLObject diff --git a/parser/source_builder.py b/telethon_generator/parser/source_builder.py similarity index 100% rename from parser/source_builder.py rename to telethon_generator/parser/source_builder.py diff --git a/parser/tlobject.py b/telethon_generator/parser/tl_object.py similarity index 100% rename from parser/tlobject.py rename to telethon_generator/parser/tl_object.py diff --git a/parser/tl_parser.py b/telethon_generator/parser/tl_parser.py similarity index 96% rename from parser/tl_parser.py rename to telethon_generator/parser/tl_parser.py index a23b1117..2e624a7c 100755 --- a/parser/tl_parser.py +++ b/telethon_generator/parser/tl_parser.py @@ -1,6 +1,6 @@ import re -from parser.tlobject import TLObject +from .tl_object import TLObject class TLParser: diff --git a/scheme.tl b/telethon_generator/scheme.tl similarity index 100% rename from scheme.tl rename to telethon_generator/scheme.tl diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py new file mode 100755 index 00000000..98cfc6c5 --- /dev/null +++ b/telethon_generator/tl_generator.py @@ -0,0 +1,401 @@ +import os +import re +import shutil + +from parser import SourceBuilder, TLParser + + +def get_output_path(normal_path): + return os.path.join('../telethon/tl', normal_path) + + +class TLGenerator: + @staticmethod + def tlobjects_exist(): + """Determines whether the TLObjects were previously generated (hence exist) or not""" + return os.path.isfile(get_output_path('all_tlobjects.py')) + + @staticmethod + def clean_tlobjects(): + """Cleans the automatically generated TLObjects from disk""" + if os.path.isdir(get_output_path('functions')): + shutil.rmtree(get_output_path('functions')) + + if os.path.isdir(get_output_path('types')): + shutil.rmtree(get_output_path('types')) + + if os.path.isfile(get_output_path('all_tlobjects.py')): + os.remove(get_output_path('all_tlobjects.py')) + + @staticmethod + def generate_tlobjects(scheme_file): + """Generates all the TLObjects from scheme.tl to tl/functions and tl/types""" + + # First ensure that the required parent directories exist + os.makedirs(get_output_path('functions'), exist_ok=True) + os.makedirs(get_output_path('types'), exist_ok=True) + + # Store the parsed file in a tuple for iterating it more than once + tlobjects = tuple(TLParser.parse_file(scheme_file)) + for tlobject in tlobjects: + # Determine the output directory and create it + out_dir = get_output_path('functions' if tlobject.is_function + else 'types') + + if tlobject.namespace: + out_dir = os.path.join(out_dir, tlobject.namespace) + + os.makedirs(out_dir, exist_ok=True) + + # Also add this object to __init__.py, so we can import the whole packet at once + init_py = os.path.join(out_dir, '__init__.py') + with open(init_py, 'a', encoding='utf-8') as file: + with SourceBuilder(file) as builder: + builder.writeln('from {} import {}'.format( + TLGenerator.get_full_file_name(tlobject), + TLGenerator.get_class_name(tlobject))) + + # Create the file for this TLObject + filename = os.path.join(out_dir, TLGenerator.get_file_name(tlobject, add_extension=True)) + with open(filename, 'w', encoding='utf-8') as file: + # Let's build the source code! + with SourceBuilder(file) as builder: + # Both types and functions inherit from MTProtoRequest so they all can be sent + builder.writeln('from telethon.tl.mtproto_request import MTProtoRequest') + builder.writeln() + builder.writeln() + builder.writeln('class {}(MTProtoRequest):'.format(TLGenerator.get_class_name(tlobject))) + + # Write the original .tl definition, along with a "generated automatically" message + builder.writeln('"""Class generated by TLObjects\' generator. ' + 'All changes will be ERASED. Original .tl definition below.') + builder.writeln('{}"""'.format(repr(tlobject))) + builder.writeln() + + # First sort the arguments so that those not being a flag come first + args = sorted([arg for arg in tlobject.args if not arg.flag_indicator], + key=lambda x: x.is_flag) + + # Then convert the args to string parameters, the flags having =None + args = [(arg.name if not arg.is_flag + else '{}=None'.format(arg.name)) for arg in args + if not arg.flag_indicator and not arg.generic_definition] + + # Write the __init__ function + if args: + builder.writeln('def __init__(self, {}):'.format(', '.join(args))) + else: + builder.writeln('def __init__(self):') + + # Now update args to have the TLObject arguments, _except_ + # those which are generated automatically: flag indicator and generic definitions. + # We don't need the generic definitions in Python because arguments can be any type + args = [arg for arg in tlobject.args + if not arg.flag_indicator and not arg.generic_definition] + + if args: + # Write the docstring, so we know the type of the arguments + builder.writeln('"""') + for arg in args: + if not arg.flag_indicator: + builder.write(':param {}: Telegram type: «{}».'.format(arg.name, arg.type)) + if arg.is_vector: + builder.write(' Must be a list.'.format(arg.name)) + if arg.is_generic: + builder.write(' This should be another MTProtoRequest.') + builder.writeln() + builder.writeln('"""') + + builder.writeln('super().__init__()') + # Functions have a result object and are confirmed by default + if tlobject.is_function: + builder.writeln('self.result = None') + builder.writeln('self.confirmed = True # Confirmed by default') + + # Create an attribute that stores the TLObject's constructor ID + builder.writeln('self.constructor_id = {}'.format(hex(tlobject.id))) + + # Set the arguments + if args: + # Leave an empty line if there are any args + builder.writeln() + for arg in args: + builder.writeln('self.{0} = {0}'.format(arg.name)) + builder.end_block() + + # Write the on_send(self, writer) function + builder.writeln('def on_send(self, writer):') + builder.writeln('writer.write_int(self.constructor_id, signed=False)' + .format(hex(tlobject.id), tlobject.name)) + + for arg in tlobject.args: + TLGenerator.write_onsend_code(builder, arg, tlobject.args) + builder.end_block() + + # Write the on_response(self, reader) function + builder.writeln('def on_response(self, reader):') + # Do not read constructor's ID, since that's already been read somewhere else + if tlobject.is_function: + builder.writeln('self.result = reader.tgread_object()') + else: + if tlobject.args: + for arg in tlobject.args: + TLGenerator.write_onresponse_code(builder, arg, tlobject.args) + else: + # If there were no arguments, we still need an on_response method, and hence "pass" if empty + builder.writeln('pass') + builder.end_block() + + # Write the __repr__(self) and __str__(self) functions + builder.writeln('def __repr__(self):') + builder.writeln("return '{}'".format(repr(tlobject))) + builder.end_block() + + builder.writeln('def __str__(self):') + builder.writeln("return {}".format(str(tlobject))) + # builder.end_block() # There is no need to end the last block + + # Once all the objects have been generated, we can now group them in a single file + filename = os.path.join(get_output_path('all_tlobjects.py')) + with open(filename, 'w', encoding='utf-8') as file: + with SourceBuilder(file) as builder: + builder.writeln('"""File generated by TLObjects\' generator. All changes will be ERASED"""') + builder.writeln() + + # First add imports + for tlobject in tlobjects: + builder.writeln('import {}'.format(TLGenerator.get_full_file_name(tlobject))) + builder.writeln() + + # Then create the dictionary containing constructor_id: class + builder.writeln('tlobjects = {') + builder.current_indent += 1 + + # Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class) + for tlobject in tlobjects: + builder.writeln('{}: {}.{},' + .format(hex(tlobject.id), + TLGenerator.get_full_file_name(tlobject), + TLGenerator.get_class_name(tlobject))) + + builder.current_indent -= 1 + builder.writeln('}') + + @staticmethod + def get_class_name(tlobject): + """Gets the class name following the Python style guidelines, in ThisClassFormat""" + + # 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(), tlobject.name) + result = result[:1].upper() + result[1:].replace('_', '') # Replace again to fully ensure! + # If it's a function, let it end with "Request" to identify them more easily + if tlobject.is_function: + result += 'Request' + return result + + @staticmethod + def get_full_file_name(tlobject): + """Gets the full file name for the given TLObject (tl.type.full.path)""" + + fullname = TLGenerator.get_file_name(tlobject, add_extension=False) + if tlobject.namespace: + fullname = '{}.{}'.format(tlobject.namespace, fullname) + + if tlobject.is_function: + return 'telethon.tl.functions.{}'.format(fullname) + else: + return 'telethon.tl.types.{}'.format(fullname) + + @staticmethod + def get_file_name(tlobject, add_extension): + """Gets the file name in file_name_format.py for the given TLObject""" + + # Courtesy of http://stackoverflow.com/a/1176023/4759433 + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', tlobject.name) + result = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + if add_extension: + return result + '.py' + else: + return result + + @staticmethod + def write_onsend_code(builder, arg, args, name=None): + """ + Writes the write code for the given argument + :param builder: The source code builder + :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 name: The name of the argument. Defaults to «self.argname» + This argument is an option because it's required when writing Vectors<> + """ + + if arg.generic_definition: + return # Do nothing, this only specifies a later type + + if name is None: + 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 + # True types are not actually sent, but instead only used to determine the flags + if arg.is_flag: + if arg.type == 'true': + return # Exit, since True type is never written + else: + builder.writeln('if {}:'.format(name)) + + if arg.is_vector: + builder.writeln("writer.write_int(0x1cb5c415, signed=False) # Vector's constructor ID") + builder.writeln('writer.write_int(len({}))'.format(name)) + builder.writeln('for {}_item in {}:'.format(arg.name, name)) + # Temporary disable .is_vector, not to enter this if again + arg.is_vector = False + TLGenerator.write_onsend_code(builder, arg, args, name='{}_item'.format(arg.name)) + arg.is_vector = True + + elif arg.flag_indicator: + # Calculate the flags with those items which are not None + builder.writeln('# Calculate the flags. This equals to those flag arguments which are NOT None') + builder.writeln('flags = 0') + for flag in args: + if flag.is_flag: + builder.writeln('flags |= (1 << {}) if {} else 0' + .format(flag.flag_index, 'self.{}'.format(flag.name))) + + builder.writeln('writer.write_int(flags)') + builder.writeln() + + elif 'int' == arg.type: + builder.writeln('writer.write_int({})'.format(name)) + + elif 'long' == arg.type: + builder.writeln('writer.write_long({})'.format(name)) + + elif 'int128' == arg.type: + builder.writeln('writer.write_large_int({}, bits=128)'.format(name)) + + elif 'int256' == arg.type: + builder.writeln('writer.write_large_int({}, bits=256)'.format(name)) + + elif 'double' == arg.type: + builder.writeln('writer.write_double({})'.format(name)) + + elif 'string' == arg.type: + builder.writeln('writer.tgwrite_string({})'.format(name)) + + elif 'Bool' == arg.type: + builder.writeln('writer.tgwrite_bool({})'.format(name)) + + elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags + pass # These are actually NOT written! Only used for flags + + elif 'bytes' == arg.type: + builder.writeln('writer.tgwrite_bytes({})'.format(name)) + + elif 'date' == arg.type: # Custom format + builder.writeln('writer.tgwrite_date({})'.format(name)) + + else: + # Else it may be a custom type + builder.writeln('{}.on_send(writer)'.format(name)) + + # End vector and flag blocks if required (if we opened them before) + if arg.is_vector: + builder.end_block() + + if arg.is_flag: + builder.end_block() + + @staticmethod + def write_onresponse_code(builder, arg, args, name=None): + """ + Writes the receive code for the given argument + + :param builder: The source code builder + :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 name: The name of the argument. Defaults to «self.argname» + This argument is an option because it's required when writing Vectors<> + """ + + if arg.generic_definition: + return # Do nothing, this only specifies a later type + + if name is None: + name = 'self.{}'.format(arg.name) + + # The argument may be a flag, only write that flag was given! + was_flag = False + if arg.is_flag: + was_flag = True + builder.writeln('if (flags & (1 << {})) != 0:'.format(arg.flag_index)) + # Temporary disable .is_flag not to enter this if again when calling the method recursively + arg.is_flag = False + + if arg.is_vector: + builder.writeln("reader.read_int() # Vector's constructor ID") + builder.writeln('{} = [] # Initialize an empty list'.format(name)) + builder.writeln('{}_len = reader.read_int()'.format(arg.name)) + builder.writeln('for _ in range({}_len):'.format(arg.name)) + # Temporary disable .is_vector, not to enter this if again + arg.is_vector = False + TLGenerator.write_onresponse_code(builder, arg, args, name='{}_item'.format(arg.name)) + builder.writeln('{}.append({}_item)'.format(name, arg.name)) + arg.is_vector = True + + elif arg.flag_indicator: + # Read the flags, which will indicate what items we should read next + builder.writeln('flags = reader.read_int()') + builder.writeln() + + elif 'int' == arg.type: + builder.writeln('{} = reader.read_int()'.format(name)) + + elif 'long' == arg.type: + builder.writeln('{} = reader.read_long()'.format(name)) + + elif 'int128' == arg.type: + builder.writeln('{} = reader.read_large_int(bits=128)'.format(name)) + + elif 'int256' == arg.type: + builder.writeln('{} = reader.read_large_int(bits=256)'.format(name)) + + elif 'double' == arg.type: + builder.writeln('{} = reader.read_double()'.format(name)) + + elif 'string' == arg.type: + builder.writeln('{} = reader.tgread_string()'.format(name)) + + elif 'Bool' == arg.type: + builder.writeln('{} = reader.tgread_bool()'.format(name)) + + elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags + builder.writeln('{} = True # Arbitrary not-None value, no need to read since it is a flag'.format(name)) + + elif 'bytes' == arg.type: + builder.writeln('{} = reader.tgread_bytes()'.format(name)) + + elif 'date' == arg.type: # Custom format + builder.writeln('{} = reader.tgread_date()'.format(name)) + + else: + # Else it may be a custom type + builder.writeln('{} = reader.tgread_object()'.format(name)) + + # End vector and flag blocks if required (if we opened them before) + if arg.is_vector: + builder.end_block() + + if was_flag: + builder.end_block() + # Restore .is_flag + arg.is_flag = True + +if __name__ == '__main__': + if TLGenerator.tlobjects_exist(): + print('Detected previous TLObjects. Cleaning...') + TLGenerator.clean_tlobjects() + + print('Generating TLObjects...') + TLGenerator.generate_tlobjects('scheme.tl') + print('Done.') diff --git a/unittests/__init__.py b/telethon_tests/__init__.py similarity index 100% rename from unittests/__init__.py rename to telethon_tests/__init__.py diff --git a/unittests/crypto_tests.py b/telethon_tests/crypto_tests.py similarity index 93% rename from unittests/crypto_tests.py rename to telethon_tests/crypto_tests.py index 8980ae00..3f0f0bff 100644 --- a/unittests/crypto_tests.py +++ b/telethon_tests/crypto_tests.py @@ -1,8 +1,8 @@ import unittest -from crypto import AES -import utils.helpers as utils -from crypto import Factorizator +from telethon.crypto import AES +import telethon.helpers as utils +from telethon.crypto import Factorizator class CryptoTests(unittest.TestCase): @@ -32,8 +32,10 @@ class CryptoTests(unittest.TestCase): def test_aes_encrypt(self): value = AES.encrypt_ige(self.plain_text, self.key, self.iv) - assert value == self.cipher_text, ('Ciphered text ("{}") does not equal expected ("{}")' - .format(value, self.cipher_text)) + take = 16 # Don't take all the bytes, since latest involve are random padding + assert value[:take] == self.cipher_text[:take],\ + ('Ciphered text ("{}") does not equal expected ("{}")' + .format(value[:take], self.cipher_text[:take])) value = AES.encrypt_ige(self.plain_text_padded, self.key, self.iv) assert value == self.cipher_text_padded, ('Ciphered text ("{}") does not equal expected ("{}")' diff --git a/unittests/network_tests.py b/telethon_tests/network_tests.py similarity index 86% rename from unittests/network_tests.py rename to telethon_tests/network_tests.py index 5947ba22..71ed37b3 100644 --- a/unittests/network_tests.py +++ b/telethon_tests/network_tests.py @@ -3,8 +3,8 @@ import socket import threading import unittest -from network import TcpTransport, TcpClient -import network.authenticator +from telethon.network import TcpTransport, TcpClient +import telethon.network.authenticator as authenticator def run_server_echo_thread(port): @@ -37,5 +37,5 @@ class NetworkTests(unittest.TestCase): @staticmethod def test_authenticator(): transport = TcpTransport('149.154.167.91', 443) - network.authenticator.do_authentication(transport) + authenticator.do_authentication(transport) transport.close() diff --git a/unittests/parser_tests.py b/telethon_tests/parser_tests.py similarity index 100% rename from unittests/parser_tests.py rename to telethon_tests/parser_tests.py diff --git a/unittests/tl_tests.py b/telethon_tests/tl_tests.py similarity index 100% rename from unittests/tl_tests.py rename to telethon_tests/tl_tests.py diff --git a/unittests/utils_tests.py b/telethon_tests/utils_tests.py similarity index 98% rename from unittests/utils_tests.py rename to telethon_tests/utils_tests.py index 3bb0c178..77aac3dd 100644 --- a/unittests/utils_tests.py +++ b/telethon_tests/utils_tests.py @@ -1,6 +1,6 @@ import os import unittest -from utils import BinaryReader, BinaryWriter +from telethon.utils import BinaryReader, BinaryWriter class UtilsTests(unittest.TestCase): diff --git a/tl/__init__.py b/tl/__init__.py deleted file mode 100755 index 5b215601..00000000 --- a/tl/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .all_tlobjects import tlobjects -from .mtproto_request import MTProtoRequest -from .session import Session - diff --git a/tl_generator.py b/tl_generator.py deleted file mode 100755 index 00ce695d..00000000 --- a/tl_generator.py +++ /dev/null @@ -1,393 +0,0 @@ -import os -import re -import shutil - -from parser import SourceBuilder, TLParser - - -def tlobjects_exist(): - """Determines whether the TLObjects were previously generated (hence exist) or not""" - return os.path.isfile('tl/all_tlobjects.py') - - -def clean_tlobjects(): - """Cleans the automatically generated TLObjects from disk""" - if os.path.isdir('tl/functions'): - shutil.rmtree('tl/functions') - - if os.path.isdir('tl/types'): - shutil.rmtree('tl/types') - - if os.path.isfile('tl/all_tlobjects.py'): - os.remove('tl/all_tlobjects.py') - - -def generate_tlobjects(scheme_file): - """Generates all the TLObjects from scheme.tl to tl/functions and tl/types""" - - # First ensure that the required parent directories exist - os.makedirs('tl/functions', exist_ok=True) - os.makedirs('tl/types', exist_ok=True) - - # Store the parsed file in a tuple for iterating it more than once - tlobjects = tuple(TLParser.parse_file(scheme_file)) - for tlobject in tlobjects: - # Determine the output directory and create it - out_dir = os.path.join('tl', - 'functions' if tlobject.is_function - else 'types') - - if tlobject.namespace: - out_dir = os.path.join(out_dir, tlobject.namespace) - - os.makedirs(out_dir, exist_ok=True) - - # Also add this object to __init__.py, so we can import the whole packet at once - init_py = os.path.join(out_dir, '__init__.py') - with open(init_py, 'a', encoding='utf-8') as file: - with SourceBuilder(file) as builder: - builder.writeln('from {} import {}'.format( - get_full_file_name(tlobject), get_class_name(tlobject))) - - # Create the file for this TLObject - filename = os.path.join(out_dir, get_file_name(tlobject, add_extension=True)) - with open(filename, 'w', encoding='utf-8') as file: - # Let's build the source code! - with SourceBuilder(file) as builder: - # Both types and functions inherit from MTProtoRequest so they all can be sent - builder.writeln('from tl.mtproto_request import MTProtoRequest') - builder.writeln() - builder.writeln() - builder.writeln('class {}(MTProtoRequest):'.format(get_class_name(tlobject))) - - # Write the original .tl definition, along with a "generated automatically" message - builder.writeln('"""Class generated by TLObjects\' generator. ' - 'All changes will be ERASED. Original .tl definition below.') - builder.writeln('{}"""'.format(repr(tlobject))) - builder.writeln() - - # First sort the arguments so that those not being a flag come first - args = sorted([arg for arg in tlobject.args if not arg.flag_indicator], - key=lambda x: x.is_flag) - - # Then convert the args to string parameters, the flags having =None - args = [(arg.name if not arg.is_flag - else '{}=None'.format(arg.name)) for arg in args - if not arg.flag_indicator and not arg.generic_definition] - - # Write the __init__ function - if args: - builder.writeln('def __init__(self, {}):'.format(', '.join(args))) - else: - builder.writeln('def __init__(self):') - - # Now update args to have the TLObject arguments, _except_ - # those which are generated automatically: flag indicator and generic definitions. - # We don't need the generic definitions in Python because arguments can be any type - args = [arg for arg in tlobject.args - if not arg.flag_indicator and not arg.generic_definition] - - if args: - # Write the docstring, so we know the type of the arguments - builder.writeln('"""') - for arg in args: - if not arg.flag_indicator: - builder.write(':param {}: Telegram type: «{}».'.format(arg.name, arg.type)) - if arg.is_vector: - builder.write(' Must be a list.'.format(arg.name)) - if arg.is_generic: - builder.write(' This should be another MTProtoRequest.') - builder.writeln() - builder.writeln('"""') - - builder.writeln('super().__init__()') - # Functions have a result object and are confirmed by default - if tlobject.is_function: - builder.writeln('self.result = None') - builder.writeln('self.confirmed = True # Confirmed by default') - - # Create an attribute that stores the TLObject's constructor ID - builder.writeln('self.constructor_id = {}'.format(hex(tlobject.id))) - - # Set the arguments - if args: - # Leave an empty line if there are any args - builder.writeln() - for arg in args: - builder.writeln('self.{0} = {0}'.format(arg.name)) - builder.end_block() - - # Write the on_send(self, writer) function - builder.writeln('def on_send(self, writer):') - builder.writeln('writer.write_int(self.constructor_id, signed=False)' - .format(hex(tlobject.id), tlobject.name)) - - for arg in tlobject.args: - write_onsend_code(builder, arg, tlobject.args) - builder.end_block() - - # Write the on_response(self, reader) function - builder.writeln('def on_response(self, reader):') - # Do not read constructor's ID, since that's already been read somewhere else - if tlobject.is_function: - builder.writeln('self.result = reader.tgread_object()') - else: - if tlobject.args: - for arg in tlobject.args: - write_onresponse_code(builder, arg, tlobject.args) - else: - # If there were no arguments, we still need an on_response method, and hence "pass" if empty - builder.writeln('pass') - builder.end_block() - - # Write the __repr__(self) and __str__(self) functions - builder.writeln('def __repr__(self):') - builder.writeln("return '{}'".format(repr(tlobject))) - builder.end_block() - - builder.writeln('def __str__(self):') - builder.writeln("return {}".format(str(tlobject))) - # builder.end_block() # There is no need to end the last block - - # Once all the objects have been generated, we can now group them in a single file - filename = os.path.join('tl', 'all_tlobjects.py') - with open(filename, 'w', encoding='utf-8') as file: - with SourceBuilder(file) as builder: - builder.writeln('"""File generated by TLObjects\' generator. All changes will be ERASED"""') - builder.writeln() - - # First add imports - for tlobject in tlobjects: - builder.writeln('import {}'.format(get_full_file_name(tlobject))) - builder.writeln() - - # Then create the dictionary containing constructor_id: class - builder.writeln('tlobjects = {') - builder.current_indent += 1 - - # Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class) - for tlobject in tlobjects: - builder.writeln('{}: {}.{},' - .format(hex(tlobject.id), get_full_file_name(tlobject), get_class_name(tlobject))) - - builder.current_indent -= 1 - builder.writeln('}') - - -def get_class_name(tlobject): - """Gets the class name following the Python style guidelines, in ThisClassFormat""" - - # 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(), tlobject.name) - result = result[:1].upper() + result[1:].replace('_', '') # Replace again to fully ensure! - # If it's a function, let it end with "Request" to identify them more easily - if tlobject.is_function: - result += 'Request' - return result - - -def get_full_file_name(tlobject): - """Gets the full file name for the given TLObject (tl.type.full.path)""" - - fullname = get_file_name(tlobject, add_extension=False) - if tlobject.namespace: - fullname = '{}.{}'.format(tlobject.namespace, fullname) - - if tlobject.is_function: - return 'tl.functions.{}'.format(fullname) - else: - return 'tl.types.{}'.format(fullname) - - -def get_file_name(tlobject, add_extension): - """Gets the file name in file_name_format.py for the given TLObject""" - - # Courtesy of http://stackoverflow.com/a/1176023/4759433 - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', tlobject.name) - result = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - if add_extension: - return result + '.py' - else: - return result - - -def write_onsend_code(builder, arg, args, name=None): - """ - Writes the write code for the given argument - :param builder: The source code builder - :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 name: The name of the argument. Defaults to «self.argname» - This argument is an option because it's required when writing Vectors<> - """ - - if arg.generic_definition: - return # Do nothing, this only specifies a later type - - if name is None: - 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 - # True types are not actually sent, but instead only used to determine the flags - if arg.is_flag: - if arg.type == 'true': - return # Exit, since True type is never written - else: - builder.writeln('if {}:'.format(name)) - - if arg.is_vector: - builder.writeln("writer.write_int(0x1cb5c415, signed=False) # Vector's constructor ID") - builder.writeln('writer.write_int(len({}))'.format(name)) - builder.writeln('for {}_item in {}:'.format(arg.name, name)) - # Temporary disable .is_vector, not to enter this if again - arg.is_vector = False - write_onsend_code(builder, arg, args, name='{}_item'.format(arg.name)) - arg.is_vector = True - - elif arg.flag_indicator: - # Calculate the flags with those items which are not None - builder.writeln('# Calculate the flags. This equals to those flag arguments which are NOT None') - builder.writeln('flags = 0') - for flag in args: - if flag.is_flag: - builder.writeln('flags |= (1 << {}) if {} else 0' - .format(flag.flag_index, 'self.{}'.format(flag.name))) - - builder.writeln('writer.write_int(flags)') - builder.writeln() - - elif 'int' == arg.type: - builder.writeln('writer.write_int({})'.format(name)) - - elif 'long' == arg.type: - builder.writeln('writer.write_long({})'.format(name)) - - elif 'int128' == arg.type: - builder.writeln('writer.write_large_int({}, bits=128)'.format(name)) - - elif 'int256' == arg.type: - builder.writeln('writer.write_large_int({}, bits=256)'.format(name)) - - elif 'double' == arg.type: - builder.writeln('writer.write_double({})'.format(name)) - - elif 'string' == arg.type: - builder.writeln('writer.tgwrite_string({})'.format(name)) - - elif 'Bool' == arg.type: - builder.writeln('writer.tgwrite_bool({})'.format(name)) - - elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags - pass # These are actually NOT written! Only used for flags - - elif 'bytes' == arg.type: - builder.writeln('writer.tgwrite_bytes({})'.format(name)) - - elif 'date' == arg.type: # Custom format - builder.writeln('writer.tgwrite_date({})'.format(name)) - - else: - # Else it may be a custom type - builder.writeln('{}.on_send(writer)'.format(name)) - - # End vector and flag blocks if required (if we opened them before) - if arg.is_vector: - builder.end_block() - - if arg.is_flag: - builder.end_block() - - -def write_onresponse_code(builder, arg, args, name=None): - """ - Writes the receive code for the given argument - - :param builder: The source code builder - :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 name: The name of the argument. Defaults to «self.argname» - This argument is an option because it's required when writing Vectors<> - """ - - if arg.generic_definition: - return # Do nothing, this only specifies a later type - - if name is None: - name = 'self.{}'.format(arg.name) - - # The argument may be a flag, only write that flag was given! - was_flag = False - if arg.is_flag: - was_flag = True - builder.writeln('if (flags & (1 << {})) != 0:'.format(arg.flag_index)) - # Temporary disable .is_flag not to enter this if again when calling the method recursively - arg.is_flag = False - - if arg.is_vector: - builder.writeln("reader.read_int() # Vector's constructor ID") - builder.writeln('{} = [] # Initialize an empty list'.format(name)) - builder.writeln('{}_len = reader.read_int()'.format(arg.name)) - builder.writeln('for _ in range({}_len):'.format(arg.name)) - # Temporary disable .is_vector, not to enter this if again - arg.is_vector = False - write_onresponse_code(builder, arg, args, name='{}_item'.format(arg.name)) - builder.writeln('{}.append({}_item)'.format(name, arg.name)) - arg.is_vector = True - - elif arg.flag_indicator: - # Read the flags, which will indicate what items we should read next - builder.writeln('flags = reader.read_int()') - builder.writeln() - - elif 'int' == arg.type: - builder.writeln('{} = reader.read_int()'.format(name)) - - elif 'long' == arg.type: - builder.writeln('{} = reader.read_long()'.format(name)) - - elif 'int128' == arg.type: - builder.writeln('{} = reader.read_large_int(bits=128)'.format(name)) - - elif 'int256' == arg.type: - builder.writeln('{} = reader.read_large_int(bits=256)'.format(name)) - - elif 'double' == arg.type: - builder.writeln('{} = reader.read_double()'.format(name)) - - elif 'string' == arg.type: - builder.writeln('{} = reader.tgread_string()'.format(name)) - - elif 'Bool' == arg.type: - builder.writeln('{} = reader.tgread_bool()'.format(name)) - - elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags - builder.writeln('{} = True # Arbitrary not-None value, no need to read since it is a flag'.format(name)) - - elif 'bytes' == arg.type: - builder.writeln('{} = reader.tgread_bytes()'.format(name)) - - elif 'date' == arg.type: # Custom format - builder.writeln('{} = reader.tgread_date()'.format(name)) - - else: - # Else it may be a custom type - builder.writeln('{} = reader.tgread_object()'.format(name)) - - # End vector and flag blocks if required (if we opened them before) - if arg.is_vector: - builder.end_block() - - if was_flag: - builder.end_block() - # Restore .is_flag - arg.is_flag = True - -if __name__ == '__main__': - if tlobjects_exist(): - print('Detected previous TLObjects. Cleaning...') - clean_tlobjects() - - print('Generating TLObjects...') - generate_tlobjects('scheme.tl') - print('Done.') diff --git a/try_telethon.py b/try_telethon.py new file mode 100644 index 00000000..938a4414 --- /dev/null +++ b/try_telethon.py @@ -0,0 +1,43 @@ +import traceback +from telethon.interactive_telegram_client import \ + InteractiveTelegramClient, print_title + + +def load_settings(path='api/settings'): + """Loads the user settings located under `api/`""" + settings = {} + with open(path, 'r', encoding='utf-8') as file: + for line in file: + value_pair = line.split('=') + left = value_pair[0].strip() + right = value_pair[1].strip() + if right.isnumeric(): + settings[left] = int(right) + else: + settings[left] = right + + return settings + + +if __name__ == '__main__': + # Load the settings and initialize the client + settings = load_settings() + client = InteractiveTelegramClient( + session_user_id=settings.get('session_name', 'anonymous'), + user_phone=str(settings['user_phone']), + layer=55, + api_id=settings['api_id'], + api_hash=settings['api_hash']) + + print('Initialization done!') + + try: + client.run() + + except Exception as e: + print('Unexpected error ({}): {} at\n{}'.format(type(e), e, traceback.format_exc())) + + finally: + print_title('Exit') + print('Thanks for trying the interactive example! Exiting...') + client.disconnect()