mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 17:36:34 +03:00
Several updates, fixes and additions (TcpClient, MtProto...)
README.md was updated to reflect more useful information More errors from the official Telegrm website have been added MtProtoSender now handles updates (and doesn't crash!) Fixes on TcpClient to be able to receive whole large packets Updated scheme.tl to the layer 55 Session is now saved more often (to prevent damages from crashes) Fixes to the code generator (generated invalid code for reading "bytes")
This commit is contained in:
parent
7802fe5487
commit
6b8a347426
28
README.md
28
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`,
|
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!
|
`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<long> = 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
|
### Plans for the future
|
||||||
If everything works well, this probably ends up being a Python package :)
|
If everything works well, this probably ends up being a Python package :)
|
||||||
|
|
||||||
|
|
126
errors.py
126
errors.py
|
@ -1,3 +1,6 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameterError(Exception):
|
class InvalidParameterError(Exception):
|
||||||
"""Occurs when an invalid parameter is given, for example,
|
"""Occurs when an invalid parameter is given, for example,
|
||||||
when either A or B are required but none is given"""
|
when either A or B are required but none is given"""
|
||||||
|
@ -19,7 +22,7 @@ class InvalidDCError(Exception):
|
||||||
def __init__(self, new_dc):
|
def __init__(self, new_dc):
|
||||||
super().__init__(self, 'Your phone number is registered to #{} DC. '
|
super().__init__(self, 'Your phone number is registered to #{} DC. '
|
||||||
'This should have been handled automatically; '
|
'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
|
self.new_dc = new_dc
|
||||||
|
|
||||||
|
@ -34,9 +37,126 @@ class InvalidChecksumError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class RPCError(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.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):
|
class BadMessageError(Exception):
|
||||||
|
|
12
main.py
12
main.py
|
@ -10,15 +10,17 @@ if __name__ == '__main__':
|
||||||
else:
|
else:
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
client = TelegramClient(session_user_id=settings.get('session_name', 'anonymous'),
|
client = TelegramClient(session_user_id=settings.get('session_name', 'anonymous'),
|
||||||
layer=54,
|
layer=55,
|
||||||
api_id=settings['api_id'],
|
api_id=settings['api_id'],
|
||||||
api_hash=settings['api_hash'])
|
api_hash=settings['api_hash'])
|
||||||
|
|
||||||
client.connect()
|
client.connect()
|
||||||
|
input('You should now be connected. Press enter when you are ready to continue.')
|
||||||
if not client.is_user_authorized():
|
if not client.is_user_authorized():
|
||||||
phone_code_hash = None
|
client.send_code_request(str(settings['user_phone']))
|
||||||
while phone_code_hash is None:
|
|
||||||
phone_code_hash = client.send_code_request(settings['user_phone'])
|
|
||||||
|
|
||||||
code = input('Enter the code you just received: ')
|
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()
|
||||||
|
|
|
@ -166,7 +166,6 @@ def do_authentication(transport):
|
||||||
sender.send(set_client_dh_params_bytes)
|
sender.send(set_client_dh_params_bytes)
|
||||||
|
|
||||||
# Step 3 response: Complete DH Exchange
|
# Step 3 response: Complete DH Exchange
|
||||||
# TODO, no more data, why did it stop again?!
|
|
||||||
with BinaryReader(sender.receive()) as reader:
|
with BinaryReader(sender.receive()) as reader:
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
if code == 0x3bcbf734: # DH Gen OK
|
if code == 0x3bcbf734: # DH Gen OK
|
||||||
|
|
|
@ -17,6 +17,12 @@ class MtProtoSender:
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.session = session
|
self.session = session
|
||||||
self.need_confirmation = [] # Message IDs that need confirmation
|
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):
|
def generate_sequence(self, confirmed):
|
||||||
"""Generates the next sequence number, based on whether it was confirmed yet or not"""
|
"""Generates the next sequence number, based on whether it was confirmed yet or not"""
|
||||||
|
@ -29,7 +35,6 @@ class MtProtoSender:
|
||||||
|
|
||||||
# region Send and receive
|
# region Send and receive
|
||||||
|
|
||||||
# TODO In TLSharp, this was async. Should this be?
|
|
||||||
def send(self, request):
|
def send(self, request):
|
||||||
"""Sends the specified MTProtoRequest, previously sending any message which needed confirmation"""
|
"""Sends the specified MTProtoRequest, previously sending any message which needed confirmation"""
|
||||||
|
|
||||||
|
@ -57,6 +62,7 @@ class MtProtoSender:
|
||||||
with BinaryReader(message) as reader:
|
with BinaryReader(message) as reader:
|
||||||
self.process_msg(remote_msg_id, remote_sequence, reader, request)
|
self.process_msg(remote_msg_id, remote_sequence, reader, request)
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Low level processing
|
# region Low level processing
|
||||||
|
@ -144,12 +150,9 @@ class MtProtoSender:
|
||||||
if code == 0x3072cfa1: # gzip_packed
|
if code == 0x3072cfa1: # gzip_packed
|
||||||
return self.handle_gzip_packed(msg_id, sequence, reader, request)
|
return self.handle_gzip_packed(msg_id, sequence, reader, request)
|
||||||
|
|
||||||
if (code == 0xe317af7e or
|
# TODO do not check by hand, keep another list of which are updates (from the .tl definition)
|
||||||
code == 0xd3f45784 or
|
updates = [0xe317af7e, 0x914fbf11, 0x16812688, 0x78d4dec1, 0x725b04c3, 0x74ae4240, 0x11f1331c]
|
||||||
code == 0x2b2fbd4e or
|
if code in updates:
|
||||||
code == 0x78d4dec1 or
|
|
||||||
code == 0x725b04c3 or
|
|
||||||
code == 0x74ae4240):
|
|
||||||
return self.handle_update(msg_id, sequence, reader)
|
return self.handle_update(msg_id, sequence, reader)
|
||||||
|
|
||||||
print('Unknown message: {}'.format(hex(msg_id)))
|
print('Unknown message: {}'.format(hex(msg_id)))
|
||||||
|
@ -160,6 +163,10 @@ class MtProtoSender:
|
||||||
# region Message handling
|
# region Message handling
|
||||||
|
|
||||||
def handle_update(self, msg_id, sequence, reader):
|
def handle_update(self, msg_id, sequence, reader):
|
||||||
|
tlobject = reader.tgread_object()
|
||||||
|
for handler in self.on_update_handlers:
|
||||||
|
handler(tlobject)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_container(self, msg_id, sequence, reader, request):
|
def handle_container(self, msg_id, sequence, reader, request):
|
||||||
|
@ -170,17 +177,9 @@ class MtProtoSender:
|
||||||
inner_sequence = reader.read_int()
|
inner_sequence = reader.read_int()
|
||||||
inner_length = reader.read_int()
|
inner_length = reader.read_int()
|
||||||
begin_position = reader.tell_position()
|
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 not self.process_msg(inner_msg_id, sequence, reader, request):
|
||||||
if error is InvalidDCError:
|
reader.set_position(begin_position + inner_length)
|
||||||
print('Re-raise {}'.format(error))
|
|
||||||
raise error
|
|
||||||
else:
|
|
||||||
print('Error while handling container {}: {}'.format(type(error), error))
|
|
||||||
reader.set_position(begin_position + inner_length)
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -231,34 +230,35 @@ class MtProtoSender:
|
||||||
def handle_rpc_result(self, msg_id, sequence, reader, mtproto_request):
|
def handle_rpc_result(self, msg_id, sequence, reader, mtproto_request):
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
request_id = reader.read_long(signed=False)
|
request_id = reader.read_long(signed=False)
|
||||||
|
inner_code = reader.read_int(signed=False)
|
||||||
|
|
||||||
if request_id == mtproto_request.msg_id:
|
if request_id == mtproto_request.msg_id:
|
||||||
mtproto_request.confirm_received = True
|
mtproto_request.confirm_received = True
|
||||||
|
|
||||||
inner_code = reader.read_int(signed=False)
|
|
||||||
if inner_code == 0x2144ca19: # RPC Error
|
if inner_code == 0x2144ca19: # RPC Error
|
||||||
error_code = reader.read_int()
|
error = RPCError(code=reader.read_int(), message=reader.tgread_string())
|
||||||
error_msg = reader.tgread_string()
|
if error.must_resend:
|
||||||
|
mtproto_request.confirm_received = False
|
||||||
|
|
||||||
if error_msg.startswith('FLOOD_WAIT_'):
|
if error.message.startswith('FLOOD_WAIT_'):
|
||||||
seconds = int(re.search(r'\d+', error_msg).group(0))
|
print('Should wait {}s. Sleeping until then.'.format(error.additional_data))
|
||||||
print('Should wait {}s. Sleeping until then.')
|
sleep(error.additional_data)
|
||||||
sleep(seconds)
|
|
||||||
|
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:
|
else:
|
||||||
raise RPCError(error_msg)
|
raise error
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
reader.seek(-4)
|
if inner_code == 0x3072cfa1: # GZip packed
|
||||||
mtproto_request.on_response(reader)
|
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):
|
def handle_gzip_packed(self, msg_id, sequence, reader, mtproto_request):
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
|
|
|
@ -23,4 +23,11 @@ class TcpClient:
|
||||||
|
|
||||||
def read(self, buffer_size):
|
def read(self, buffer_size):
|
||||||
"""Reads (receives) the specified bytes from the connected peer"""
|
"""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)
|
||||||
|
|
|
@ -20,7 +20,6 @@ class TcpTransport:
|
||||||
# Get a TcpMessage which contains the given packet
|
# Get a TcpMessage which contains the given packet
|
||||||
tcp_message = TcpMessage(self._send_counter, 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._tcp_client.write(tcp_message.encode())
|
||||||
self._send_counter += 1
|
self._send_counter += 1
|
||||||
|
|
||||||
|
@ -48,6 +47,6 @@ class TcpTransport:
|
||||||
# If we passed the tests, we can then return a valid TcpMessage
|
# If we passed the tests, we can then return a valid TcpMessage
|
||||||
return TcpMessage(seq, body)
|
return TcpMessage(seq, body)
|
||||||
|
|
||||||
def dispose(self):
|
def close(self):
|
||||||
if self._tcp_client.connected:
|
if self._tcp_client.connected:
|
||||||
self._tcp_client.close()
|
self._tcp_client.close()
|
||||||
|
|
16
scheme.tl
16
scheme.tl
|
@ -155,6 +155,8 @@ inputMediaUploadedThumbDocument#ad613491 file:InputFile thumb:InputFile mime_typ
|
||||||
inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia;
|
inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia;
|
||||||
inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia;
|
inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia;
|
||||||
inputMediaGifExternal#4843b0fd url:string q: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;
|
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||||
inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = 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.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;
|
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;
|
updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
|
||||||
updateReadFeaturedStickers#571d2742 = Update;
|
updateReadFeaturedStickers#571d2742 = Update;
|
||||||
updateRecentStickers#9a422c20 = 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;
|
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;
|
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<DcOption> 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<DisabledFeature> = Config;
|
config#9a6b2e2a flags:# date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> 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<DisabledFeature> = Config;
|
||||||
|
|
||||||
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
|
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;
|
keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
|
||||||
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
|
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
|
||||||
keyboardButtonRequestGeoLocation#fc796b3f 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<KeyboardButton> = KeyboardButtonRow;
|
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||||
|
|
||||||
|
@ -672,7 +676,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
|
||||||
auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
|
auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
|
||||||
auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = 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;
|
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.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates;
|
||||||
messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
|
messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
|
||||||
messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates;
|
messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates;
|
||||||
messages.sendBroadcast#bf73f4da contacts:Vector<InputUser> random_id:Vector<long> message:string media:InputMedia = Updates;
|
|
||||||
messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
|
messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
|
||||||
messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
|
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;
|
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.exportMessageLink#c846d22d channel:InputChannel id:int = ExportedMessageLink;
|
||||||
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
||||||
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
|
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
|
||||||
|
channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats;
|
||||||
|
|
||||||
// LAYER 54
|
// LAYER 55
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# This file is based on TLSharp
|
# This file is based on TLSharp
|
||||||
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/TelegramClient.cs
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/TelegramClient.cs
|
||||||
import re
|
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
|
@ -9,11 +8,11 @@ from network import MtProtoSender, TcpTransport
|
||||||
from errors import *
|
from errors import *
|
||||||
|
|
||||||
from tl import Session
|
from tl import Session
|
||||||
from tl.types import InputPeerUser
|
from tl.types import InputPeerUser, InputPeerEmpty
|
||||||
from tl.functions import InvokeWithLayerRequest, InitConnectionRequest
|
from tl.functions import InvokeWithLayerRequest, InitConnectionRequest
|
||||||
from tl.functions.help import GetConfigRequest
|
from tl.functions.help import GetConfigRequest
|
||||||
from tl.functions.auth import CheckPhoneRequest, SendCodeRequest, SignInRequest
|
from tl.functions.auth import CheckPhoneRequest, SendCodeRequest, SignInRequest
|
||||||
from tl.functions.messages import SendMessageRequest
|
from tl.functions.messages import GetDialogsRequest, SendMessageRequest
|
||||||
|
|
||||||
|
|
||||||
class TelegramClient:
|
class TelegramClient:
|
||||||
|
@ -32,28 +31,32 @@ class TelegramClient:
|
||||||
# These will be set later
|
# These will be set later
|
||||||
self.dc_options = None
|
self.dc_options = None
|
||||||
self.sender = None
|
self.sender = None
|
||||||
|
self.phone_code_hash = None
|
||||||
|
|
||||||
# TODO Should this be async?
|
|
||||||
def connect(self, reconnect=False):
|
def connect(self, reconnect=False):
|
||||||
if not self.session.auth_key or reconnect:
|
if not self.session.auth_key or reconnect:
|
||||||
self.session.auth_key, self.session.time_offset = network.authenticator.do_authentication(self.transport)
|
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 = MtProtoSender(self.transport, self.session)
|
||||||
|
self.sender.add_update_handler(self.on_update)
|
||||||
|
|
||||||
if not reconnect:
|
# Always init connection by using the latest layer, not only when not reconnecting (as in original TLSharp's)
|
||||||
request = InvokeWithLayerRequest(layer=self.layer,
|
# Otherwise, the server thinks that were using the oldest layer!
|
||||||
query=InitConnectionRequest(api_id=self.api_id,
|
# (Note that this is mainly untested, but it seems like it since some errors point in that direction)
|
||||||
device_model=platform.node(),
|
request = InvokeWithLayerRequest(layer=self.layer,
|
||||||
system_version=platform.system(),
|
query=InitConnectionRequest(api_id=self.api_id,
|
||||||
app_version='0.1',
|
device_model=platform.node(),
|
||||||
lang_code='en',
|
system_version=platform.system(),
|
||||||
query=GetConfigRequest()))
|
app_version='0.1',
|
||||||
|
lang_code='en',
|
||||||
|
query=GetConfigRequest()))
|
||||||
|
|
||||||
self.sender.send(request)
|
self.sender.send(request)
|
||||||
self.sender.receive(request)
|
self.sender.receive(request)
|
||||||
|
|
||||||
# Result is a Config TLObject
|
# Result is a Config TLObject
|
||||||
self.dc_options = request.result.dc_options
|
self.dc_options = request.result.dc_options
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -64,9 +67,11 @@ class TelegramClient:
|
||||||
# dc is a DcOption TLObject
|
# dc is a DcOption TLObject
|
||||||
dc = next(dc for dc in self.dc_options if dc.id == dc_id)
|
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.transport = TcpTransport(dc.ip_address, dc.port)
|
||||||
self.session.server_address = dc.ip_address
|
self.session.server_address = dc.ip_address
|
||||||
self.session.port = dc.port
|
self.session.port = dc.port
|
||||||
|
self.session.save()
|
||||||
|
|
||||||
self.connect(reconnect=True)
|
self.connect(reconnect=True)
|
||||||
|
|
||||||
|
@ -84,7 +89,6 @@ class TelegramClient:
|
||||||
return request.result.phone_registered
|
return request.result.phone_registered
|
||||||
|
|
||||||
def send_code_request(self, phone_number):
|
def send_code_request(self, phone_number):
|
||||||
"""May return None if an error occured!"""
|
|
||||||
request = SendCodeRequest(phone_number, self.api_id, self.api_hash)
|
request = SendCodeRequest(phone_number, self.api_id, self.api_hash)
|
||||||
completed = False
|
completed = False
|
||||||
while not completed:
|
while not completed:
|
||||||
|
@ -92,17 +96,17 @@ class TelegramClient:
|
||||||
self.sender.send(request)
|
self.sender.send(request)
|
||||||
self.sender.receive(request)
|
self.sender.receive(request)
|
||||||
completed = True
|
completed = True
|
||||||
|
if request.result:
|
||||||
|
self.phone_code_hash = request.result.phone_code_hash
|
||||||
|
|
||||||
except InvalidDCError as error:
|
except InvalidDCError as error:
|
||||||
self.reconnect_to_dc(error.new_dc)
|
self.reconnect_to_dc(error.new_dc)
|
||||||
|
|
||||||
if request.result is None:
|
def make_auth(self, phone_number, code):
|
||||||
return None
|
if not self.phone_code_hash:
|
||||||
else:
|
raise ValueError('Please make sure you have called send_code_request first!')
|
||||||
return request.result.phone_code_hash
|
|
||||||
|
|
||||||
|
request = SignInRequest(phone_number, self.phone_code_hash, code)
|
||||||
def make_auth(self, phone_number, phone_code_hash, code):
|
|
||||||
request = SignInRequest(phone_number, phone_code_hash, code)
|
|
||||||
self.sender.send(request)
|
self.sender.send(request)
|
||||||
self.sender.receive(request)
|
self.sender.receive(request)
|
||||||
|
|
||||||
|
@ -112,9 +116,25 @@ class TelegramClient:
|
||||||
|
|
||||||
return self.session.user
|
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):
|
def send_message(self, user, message):
|
||||||
peer = InputPeerUser(user.id, user.access_hash)
|
peer = InputPeerUser(user.id, user.access_hash)
|
||||||
request = SendMessageRequest(peer, message, utils.generate_random_long())
|
request = SendMessageRequest(peer, message, utils.generate_random_long())
|
||||||
|
|
||||||
self.sender.send(request)
|
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)))
|
||||||
|
|
|
@ -93,8 +93,8 @@ class TLObject:
|
||||||
valid_args = [arg for arg in self.args
|
valid_args = [arg for arg in self.args
|
||||||
if not arg.flag_indicator and not arg.generic_definition]
|
if not arg.flag_indicator and not arg.generic_definition]
|
||||||
|
|
||||||
args = ', '.join(['{} = {{}}'.format(arg.name) for arg in valid_args])
|
args = ', '.join(['{}={{}}'.format(arg.name) for arg in valid_args])
|
||||||
args_format = ', '.join(['self.{}'.format(arg.name) for arg in valid_args])
|
args_format = ', '.join(['str(self.{})'.format(arg.name) for arg in valid_args])
|
||||||
|
|
||||||
return ("'({} (ID: {}) = ({}))'.format({})"
|
return ("'({} (ID: {}) = ({}))'.format({})"
|
||||||
.format(fullname, hex(self.id), args, args_format))
|
.format(fullname, hex(self.id), args, args_format))
|
||||||
|
|
|
@ -63,7 +63,7 @@ def generate_tlobjects(scheme_file):
|
||||||
# Write the original .tl definition, along with a "generated automatically" message
|
# Write the original .tl definition, along with a "generated automatically" message
|
||||||
builder.writeln('"""Class generated by TLObjects\' generator. '
|
builder.writeln('"""Class generated by TLObjects\' generator. '
|
||||||
'All changes will be ERASED. Original .tl definition below.')
|
'All changes will be ERASED. Original .tl definition below.')
|
||||||
builder.writeln('{}"""'.format(tlobject))
|
builder.writeln('{}"""'.format(repr(tlobject)))
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
# First sort the arguments so that those not being a flag come first
|
# 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))
|
builder.writeln('{} = True # Arbitrary not-None value, no need to read since it is a flag'.format(name))
|
||||||
|
|
||||||
elif 'bytes' == arg.type:
|
elif 'bytes' == arg.type:
|
||||||
builder.writeln('{} = reader.read()'.format(name))
|
builder.writeln('{} = reader.tgread_bytes()'.format(name))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Else it may be a custom type
|
# Else it may be a custom type
|
||||||
|
|
|
@ -267,7 +267,7 @@ class UnitTest(unittest.TestCase):
|
||||||
def test_authenticator():
|
def test_authenticator():
|
||||||
transport = TcpTransport('149.154.167.91', 443)
|
transport = TcpTransport('149.154.167.91', 443)
|
||||||
network.authenticator.do_authentication(transport)
|
network.authenticator.do_authentication(transport)
|
||||||
transport.dispose()
|
transport.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user