diff --git a/README.md b/README.md index 672a22e4..ef2e753e 100755 --- a/README.md +++ b/README.md @@ -15,6 +15,34 @@ Also, you need to obtain your both [API ID and Hash](my.telegram.org). Once you the `settings_example` file, naming it `settings` (lowercase!). Then fill the file with the corresponding values (your `api_id`, `api_hash` and phone number in international format). Now it is when you're ready to go! +### How to add more functions to TelegramClient +As of now, you cannot call any Telegram function unless you first write it by hand under `tl/telegram_client.py`. Why? +Every Telegram function (or _request_) work in its own way. In some, you may only be interested in a single result field, +and in others you may need to format the result in a different way. However, a plan for the future is to be able to call +any function by giving its `namespace.name` and passing the arguments. But until that happens, to add a new function do: + +1. Have a look under `tl/functions/` and find the `Request` that suits your needs. +2. Have a look inside that `Request` you chose, and find what arguments and in what order you'll need to call it. +3. Import it in `tl/telegram_client.py` by using `from tl.functions import SomeTelegramRequest`. +4. Add a new method, or function, that looks as follows: +```python +def my_function(self, my_arguments): + request = SomeTelegramRequest(my_arguments) + + self.sender.send(request) + self.sender.receive(request) + + return request.result +``` +5. To determine how the result will look like, simply look at the original `.tl` definition. After the `=`, +you will see the type. Let's see an example: +`stickerPack#12b299d4 emoticon:string documents:Vector = StickerPack;` +As it turns out, the result is going to be an `StickerPack`. Without a second doubt, head into `tl/types/` and find it; +open the file and see what the result will look like. Alternatively, you can simply `print(str(request.result))`! + +Be warned that there may be more than one different type on the results. This is due to Telegram's polymorphism, +for example, a message may or not be empty, etc. + ### Plans for the future If everything works well, this probably ends up being a Python package :) diff --git a/errors.py b/errors.py index 243c2cac..fc0a6a49 100644 --- a/errors.py +++ b/errors.py @@ -1,3 +1,6 @@ +import re + + class InvalidParameterError(Exception): """Occurs when an invalid parameter is given, for example, when either A or B are required but none is given""" @@ -19,7 +22,7 @@ class InvalidDCError(Exception): def __init__(self, new_dc): super().__init__(self, 'Your phone number is registered to #{} DC. ' 'This should have been handled automatically; ' - 'if it has not, please restart the app.') + 'if it has not, please restart the app.'.format(new_dc)) self.new_dc = new_dc @@ -34,9 +37,126 @@ class InvalidChecksumError(Exception): class RPCError(Exception): - def __init__(self, message): - super().__init__(self, message) + + CodeMessages = { + 303: ('ERROR_SEE_OTHER', 'The request must be repeated, but directed to a different data center.'), + + 400: ('BAD_REQUEST', 'The query contains errors. In the event that a request was created using a ' + 'form and contains user generated data, the user should be notified that the ' + 'data must be corrected before the query is repeated.'), + + 401: ('UNAUTHORIZED', 'There was an unauthorized attempt to use functionality available only to ' + 'authorized users.'), + + 403: ('FORBIDDEN', 'Privacy violation. For example, an attempt to write a message to someone who ' + 'has blacklisted the current user.'), + + 404: ('NOT_FOUND', 'An attempt to invoke a non-existent object, such as a method.'), + + 420: ('FLOOD', 'The maximum allowed number of attempts to invoke the given method with ' + 'the given input parameters has been exceeded. For example, in an attempt ' + 'to request a large number of text messages (SMS) for the same phone number.'), + + 500: ('INTERNAL', 'An internal server error occurred while a request was being processed; ' + 'for example, there was a disruption while accessing a database or file storage.') + } + + ErrorMessages = { + # 303 ERROR_SEE_OTHER + 'FILE_MIGRATE_(\d+)': 'The file to be accessed is currently stored in a different data center (#{}).', + + 'PHONE_MIGRATE_(\d+)': 'The phone number a user is trying to use for authorization is associated ' + 'with a different data center (#{}).', + + 'NETWORK_MIGRATE_(\d+)': 'The source IP address is associated with a different data center (#{}, ' + 'for registration).', + + 'USER_MIGRATE_(\d+)': 'The user whose identity is being used to execute queries is associated with ' + 'a different data center (#{} for registration).', + + # 400 BAD_REQUEST + 'FIRSTNAME_INVALID': 'The first name is invalid.', + + 'LASTNAME_INVALID': 'The last name is invalid.', + + 'PHONE_NUMBER_INVALID': 'The phone number is invalid.', + + 'PHONE_CODE_HASH_EMPTY': 'phone_code_hash is missing.', + + 'PHONE_CODE_EMPTY': 'phone_code is missing.', + + 'PHONE_CODE_EXPIRED': 'The confirmation code has expired.', + + 'API_ID_INVALID': 'The api_id/api_hash combination is invalid.', + + 'PHONE_NUMBER_OCCUPIED': 'The phone number is already in use.', + + 'PHONE_NUMBER_UNOCCUPIED': 'The phone number is not yet being used.', + + 'USERS_TOO_FEW': 'Not enough users (to create a chat, for example).', + + 'USERS_TOO_MUCH': 'The maximum number of users has been exceeded (to create a chat, for example).', + + 'TYPE_CONSTRUCTOR_INVALID': 'The type constructor is invalid.', + + 'FILE_PART_INVALID': 'The file part number is invalid.', + + 'FILE_PARTS_INVALID': 'The number of file parts is invalid.', + + 'FILE_PART_(\d+)_MISSING': 'Part {} of the file is missing from storage.', + + 'MD5_CHECKSUM_INVALID': 'The MD5 checksums do not match.', + + 'PHOTO_INVALID_DIMENSIONS': 'The photo dimensions are invalid.', + + 'FIELD_NAME_INVALID': 'The field with the name FIELD_NAME is invalid.', + + 'FIELD_NAME_EMPTY': 'The field with the name FIELD_NAME is missing.', + + 'MSG_WAIT_FAILED': 'A waiting call returned an error.', + + # 401 UNAUTHORIZED + 'AUTH_KEY_UNREGISTERED': 'The key is not registered in the system.', + + 'AUTH_KEY_INVALID': 'The key is invalid.', + + 'USER_DEACTIVATED': 'The user has been deleted/deactivated.', + + 'SESSION_REVOKED': 'The authorization has been invalidated, because of the user terminating all sessions.', + + 'SESSION_EXPIRED': 'The authorization has expired.', + + 'ACTIVE_USER_REQUIRED': 'The method is only available to already activated users.', + + 'AUTH_KEY_PERM_EMPTY': 'The method is unavailable for temporary authorization key, not bound to permanent.', + + # 420 FLOOD + 'FLOOD_WAIT_(\d+)': 'A wait of {} seconds is required.' + } + + def __init__(self, code, message): + self.code = code + self.code_meaning = RPCError.CodeMessages[code] + self.message = message + self.must_resend = code == 303 # ERROR_SEE_OTHER, "The request must be repeated" + + called_super = False + for key, error_msg in RPCError.ErrorMessages.items(): + match = re.match(key, message) + if match: + # Get additional_data, if any + if match.groups(): + self.additional_data = int(match.group(1)) + else: + self.additional_data = None + + super().__init__(self, error_msg) + called_super = True + break + + if not called_super: + super().__init__(self, 'Unknown error message with code {}: {}'.format(code, message)) class BadMessageError(Exception): diff --git a/main.py b/main.py index 2446ba96..d8971381 100755 --- a/main.py +++ b/main.py @@ -10,15 +10,17 @@ if __name__ == '__main__': else: settings = load_settings() client = TelegramClient(session_user_id=settings.get('session_name', 'anonymous'), - layer=54, + layer=55, api_id=settings['api_id'], api_hash=settings['api_hash']) client.connect() + input('You should now be connected. Press enter when you are ready to continue.') if not client.is_user_authorized(): - phone_code_hash = None - while phone_code_hash is None: - phone_code_hash = client.send_code_request(settings['user_phone']) + client.send_code_request(str(settings['user_phone'])) code = input('Enter the code you just received: ') - client.make_auth(settings['user_phone'], phone_code_hash, code) + client.make_auth(settings['user_phone'], code) + + else: + client.get_dialogs() diff --git a/network/authenticator.py b/network/authenticator.py index 14bcfce0..608f006d 100755 --- a/network/authenticator.py +++ b/network/authenticator.py @@ -166,7 +166,6 @@ def do_authentication(transport): sender.send(set_client_dh_params_bytes) # Step 3 response: Complete DH Exchange - # TODO, no more data, why did it stop again?! with BinaryReader(sender.receive()) as reader: code = reader.read_int(signed=False) if code == 0x3bcbf734: # DH Gen OK diff --git a/network/mtproto_sender.py b/network/mtproto_sender.py index cbe0b9f7..019a0991 100755 --- a/network/mtproto_sender.py +++ b/network/mtproto_sender.py @@ -17,6 +17,12 @@ class MtProtoSender: self.transport = transport self.session = session self.need_confirmation = [] # Message IDs that need confirmation + self.on_update_handlers = [] + + def add_update_handler(self, handler): + """Adds an update handler (a method with one argument, the received TLObject) + that is fired when there are updates available""" + self.on_update_handlers.append(handler) def generate_sequence(self, confirmed): """Generates the next sequence number, based on whether it was confirmed yet or not""" @@ -29,7 +35,6 @@ class MtProtoSender: # region Send and receive - # TODO In TLSharp, this was async. Should this be? def send(self, request): """Sends the specified MTProtoRequest, previously sending any message which needed confirmation""" @@ -57,6 +62,7 @@ class MtProtoSender: with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader, request) + # endregion # region Low level processing @@ -144,12 +150,9 @@ class MtProtoSender: if code == 0x3072cfa1: # gzip_packed return self.handle_gzip_packed(msg_id, sequence, reader, request) - if (code == 0xe317af7e or - code == 0xd3f45784 or - code == 0x2b2fbd4e or - code == 0x78d4dec1 or - code == 0x725b04c3 or - code == 0x74ae4240): + # TODO do not check by hand, keep another list of which are updates (from the .tl definition) + updates = [0xe317af7e, 0x914fbf11, 0x16812688, 0x78d4dec1, 0x725b04c3, 0x74ae4240, 0x11f1331c] + if code in updates: return self.handle_update(msg_id, sequence, reader) print('Unknown message: {}'.format(hex(msg_id))) @@ -160,6 +163,10 @@ class MtProtoSender: # region Message handling def handle_update(self, msg_id, sequence, reader): + tlobject = reader.tgread_object() + for handler in self.on_update_handlers: + handler(tlobject) + return False def handle_container(self, msg_id, sequence, reader, request): @@ -170,17 +177,9 @@ class MtProtoSender: inner_sequence = reader.read_int() inner_length = reader.read_int() begin_position = reader.tell_position() - try: - if not self.process_msg(inner_msg_id, sequence, reader, request): - reader.set_position(begin_position + inner_length) - except Exception as error: - if error is InvalidDCError: - print('Re-raise {}'.format(error)) - raise error - else: - print('Error while handling container {}: {}'.format(type(error), error)) - reader.set_position(begin_position + inner_length) + if not self.process_msg(inner_msg_id, sequence, reader, request): + reader.set_position(begin_position + inner_length) return False @@ -231,34 +230,35 @@ class MtProtoSender: def handle_rpc_result(self, msg_id, sequence, reader, mtproto_request): code = reader.read_int(signed=False) request_id = reader.read_long(signed=False) + inner_code = reader.read_int(signed=False) if request_id == mtproto_request.msg_id: mtproto_request.confirm_received = True - inner_code = reader.read_int(signed=False) if inner_code == 0x2144ca19: # RPC Error - error_code = reader.read_int() - error_msg = reader.tgread_string() + error = RPCError(code=reader.read_int(), message=reader.tgread_string()) + if error.must_resend: + mtproto_request.confirm_received = False - if error_msg.startswith('FLOOD_WAIT_'): - seconds = int(re.search(r'\d+', error_msg).group(0)) - print('Should wait {}s. Sleeping until then.') - sleep(seconds) + if error.message.startswith('FLOOD_WAIT_'): + print('Should wait {}s. Sleeping until then.'.format(error.additional_data)) + sleep(error.additional_data) + + elif error.message.startswith('PHONE_MIGRATE_'): + raise InvalidDCError(error.additional_data) - elif error_msg.startswith('PHONE_MIGRATE_'): - dc_index = int(re.search(r'\d+', error_msg).group(0)) - raise InvalidDCError(dc_index) else: - raise RPCError(error_msg) - - elif inner_code == 0x3072cfa1: # GZip packed - unpacked_data = gzip.decompress(reader.tgread_bytes()) - with BinaryReader(unpacked_data) as compressed_reader: - mtproto_request.on_response(compressed_reader) + raise error else: - reader.seek(-4) - mtproto_request.on_response(reader) + if inner_code == 0x3072cfa1: # GZip packed + unpacked_data = gzip.decompress(reader.tgread_bytes()) + with BinaryReader(unpacked_data) as compressed_reader: + mtproto_request.on_response(compressed_reader) + + else: + reader.seek(-4) + mtproto_request.on_response(reader) def handle_gzip_packed(self, msg_id, sequence, reader, mtproto_request): code = reader.read_int(signed=False) diff --git a/network/tcp_client.py b/network/tcp_client.py index baf040d3..abbc59b4 100755 --- a/network/tcp_client.py +++ b/network/tcp_client.py @@ -23,4 +23,11 @@ class TcpClient: def read(self, buffer_size): """Reads (receives) the specified bytes from the connected peer""" - return self.socket.recv(buffer_size) + # TODO improve (don't cast to list, use a mutable byte list instead or similar, see recv_into) + result = [] + while len(result) < buffer_size: + left_data = buffer_size - len(result) + partial = self.socket.recv(left_data) + result.extend(list(partial)) + + return bytes(result) diff --git a/network/tcp_transport.py b/network/tcp_transport.py index 12379896..8f85bd6d 100755 --- a/network/tcp_transport.py +++ b/network/tcp_transport.py @@ -20,7 +20,6 @@ class TcpTransport: # Get a TcpMessage which contains the given packet tcp_message = TcpMessage(self._send_counter, packet) - # TODO In TLSharp, this is async; Should both send and receive be here too? self._tcp_client.write(tcp_message.encode()) self._send_counter += 1 @@ -48,6 +47,6 @@ class TcpTransport: # If we passed the tests, we can then return a valid TcpMessage return TcpMessage(seq, body) - def dispose(self): + def close(self): if self._tcp_client.connected: self._tcp_client.close() diff --git a/scheme.tl b/scheme.tl index 4d5a4911..d649d004 100755 --- a/scheme.tl +++ b/scheme.tl @@ -155,6 +155,8 @@ inputMediaUploadedThumbDocument#ad613491 file:InputFile thumb:InputFile mime_typ inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia; inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia; inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; +inputMediaPhotoExternal#b55f4f18 url:string caption:string = InputMedia; +inputMediaDocumentExternal#e5e9607c url:string caption:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto; @@ -268,7 +270,7 @@ auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone; auth.sentCode#5e002502 flags:# phone_registered:flags.0?true type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; -auth.authorization#ff036af1 user:User = auth.Authorization; +auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization; @@ -394,6 +396,8 @@ updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; +updateConfig#a229dd06 = Update; +updatePtsChanged#3354678f = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -418,7 +422,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true id:int ip_address:string port:int = DcOption; -config#f401a4bf date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int disabled_features:Vector = Config; +config#9a6b2e2a flags:# date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int tmp_sessions:flags.0?int disabled_features:Vector = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -568,7 +572,7 @@ keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; -keyboardButtonSwitchInline#ea1b7a14 text:string query:string = KeyboardButton; +keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -672,7 +676,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; -messages.botCallbackAnswer#b10df1fb flags:# alert:flags.1?true message:flags.0?string url:flags.2?string = messages.BotCallbackAnswer; +messages.botCallbackAnswer#b10df1fb flags:# alert:flags.1?true has_url:flags.3?true message:flags.0?string url:flags.2?string = messages.BotCallbackAnswer; messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; @@ -804,7 +808,6 @@ messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Upda messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates; -messages.sendBroadcast#bf73f4da contacts:Vector random_id:Vector message:string media:InputMedia = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; @@ -903,5 +906,6 @@ channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates; channels.exportMessageLink#c846d22d channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates; +channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; -// LAYER 54 +// LAYER 55 diff --git a/tl/telegram_client.py b/tl/telegram_client.py index a37c46c3..77638c76 100644 --- a/tl/telegram_client.py +++ b/tl/telegram_client.py @@ -1,6 +1,5 @@ # This file is based on TLSharp # https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/TelegramClient.cs -import re import platform import utils @@ -9,11 +8,11 @@ from network import MtProtoSender, TcpTransport from errors import * from tl import Session -from tl.types import InputPeerUser +from tl.types import InputPeerUser, InputPeerEmpty from tl.functions import InvokeWithLayerRequest, InitConnectionRequest from tl.functions.help import GetConfigRequest from tl.functions.auth import CheckPhoneRequest, SendCodeRequest, SignInRequest -from tl.functions.messages import SendMessageRequest +from tl.functions.messages import GetDialogsRequest, SendMessageRequest class TelegramClient: @@ -32,28 +31,32 @@ class TelegramClient: # These will be set later self.dc_options = None self.sender = None + self.phone_code_hash = None - # TODO Should this be async? def connect(self, reconnect=False): if not self.session.auth_key or reconnect: self.session.auth_key, self.session.time_offset = network.authenticator.do_authentication(self.transport) + self.session.save() self.sender = MtProtoSender(self.transport, self.session) + self.sender.add_update_handler(self.on_update) - if not reconnect: - request = InvokeWithLayerRequest(layer=self.layer, - query=InitConnectionRequest(api_id=self.api_id, - device_model=platform.node(), - system_version=platform.system(), - app_version='0.1', - lang_code='en', - query=GetConfigRequest())) + # Always init connection by using the latest layer, not only when not reconnecting (as in original TLSharp's) + # Otherwise, the server thinks that were using the oldest layer! + # (Note that this is mainly untested, but it seems like it since some errors point in that direction) + request = InvokeWithLayerRequest(layer=self.layer, + query=InitConnectionRequest(api_id=self.api_id, + device_model=platform.node(), + system_version=platform.system(), + app_version='0.1', + lang_code='en', + query=GetConfigRequest())) - self.sender.send(request) - self.sender.receive(request) + self.sender.send(request) + self.sender.receive(request) - # Result is a Config TLObject - self.dc_options = request.result.dc_options + # Result is a Config TLObject + self.dc_options = request.result.dc_options return True @@ -64,9 +67,11 @@ class TelegramClient: # dc is a DcOption TLObject dc = next(dc for dc in self.dc_options if dc.id == dc_id) + self.transport.close() self.transport = TcpTransport(dc.ip_address, dc.port) self.session.server_address = dc.ip_address self.session.port = dc.port + self.session.save() self.connect(reconnect=True) @@ -84,7 +89,6 @@ class TelegramClient: return request.result.phone_registered def send_code_request(self, phone_number): - """May return None if an error occured!""" request = SendCodeRequest(phone_number, self.api_id, self.api_hash) completed = False while not completed: @@ -92,17 +96,17 @@ class TelegramClient: self.sender.send(request) self.sender.receive(request) completed = True + if request.result: + self.phone_code_hash = request.result.phone_code_hash + except InvalidDCError as error: self.reconnect_to_dc(error.new_dc) - if request.result is None: - return None - else: - return request.result.phone_code_hash + def make_auth(self, phone_number, code): + if not self.phone_code_hash: + raise ValueError('Please make sure you have called send_code_request first!') - - def make_auth(self, phone_number, phone_code_hash, code): - request = SignInRequest(phone_number, phone_code_hash, code) + request = SignInRequest(phone_number, self.phone_code_hash, code) self.sender.send(request) self.sender.receive(request) @@ -112,9 +116,25 @@ class TelegramClient: return self.session.user + def get_dialogs(self): + request = GetDialogsRequest(offset_date=0, + offset_id=0, + offset_peer=InputPeerEmpty(), + limit=20) + + self.sender.send(request) + self.sender.receive(request) + + print(request.result) + def send_message(self, user, message): peer = InputPeerUser(user.id, user.access_hash) request = SendMessageRequest(peer, message, utils.generate_random_long()) self.sender.send(request) - self.sender.send(request) + self.sender.receive(request) + + def on_update(self, tlobject): + """This method is fired when there are updates from Telegram. + Add your own implementation below, or simply override it!""" + print('We have an update: {}'.format(str(tlobject))) diff --git a/tl/tlobject.py b/tl/tlobject.py index f6434bdf..6c1566f6 100755 --- a/tl/tlobject.py +++ b/tl/tlobject.py @@ -93,8 +93,8 @@ class TLObject: valid_args = [arg for arg in self.args if not arg.flag_indicator and not arg.generic_definition] - args = ', '.join(['{} = {{}}'.format(arg.name) for arg in valid_args]) - args_format = ', '.join(['self.{}'.format(arg.name) for arg in valid_args]) + args = ', '.join(['{}={{}}'.format(arg.name) for arg in valid_args]) + args_format = ', '.join(['str(self.{})'.format(arg.name) for arg in valid_args]) return ("'({} (ID: {}) = ({}))'.format({})" .format(fullname, hex(self.id), args, args_format)) diff --git a/tl_generator.py b/tl_generator.py index 8428c8a5..2231a81a 100755 --- a/tl_generator.py +++ b/tl_generator.py @@ -63,7 +63,7 @@ def generate_tlobjects(scheme_file): # 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(tlobject)) + builder.writeln('{}"""'.format(repr(tlobject))) builder.writeln() # First sort the arguments so that those not being a flag come first @@ -359,7 +359,7 @@ def write_onresponse_code(builder, arg, args, name=None): 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.read()'.format(name)) + builder.writeln('{} = reader.tgread_bytes()'.format(name)) else: # Else it may be a custom type diff --git a/unit_test.py b/unit_test.py index d6642baf..fe2c67da 100755 --- a/unit_test.py +++ b/unit_test.py @@ -267,7 +267,7 @@ class UnitTest(unittest.TestCase): def test_authenticator(): transport = TcpTransport('149.154.167.91', 443) network.authenticator.do_authentication(transport) - transport.dispose() + transport.close() if __name__ == '__main__': unittest.main()