mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-24 18:33:44 +03:00
Added and updated documentation
This commit is contained in:
parent
5af1a4a5fc
commit
bd1fee4048
23
README.md
23
README.md
|
@ -1,21 +1,27 @@
|
||||||
# Telethon
|
# Telethon
|
||||||
**Telethon** is Telegram client implementation in Python. This project's _core_ is **completely based** on [TLSharp](https://github.com/sochix/TLSharp), so please, also have a look to the original project!
|
**Telethon** is Telegram client implementation in Python. This project's _core_ is **completely based** on
|
||||||
|
[TLSharp](https://github.com/sochix/TLSharp). All the files which are fully based on it will have a notice
|
||||||
|
on the top of the file. Also don't forget to have a look to the original project.
|
||||||
|
|
||||||
Other parts, such as the request themselves, the .tl tokenizer and code generator, or some ported C# utilities such as `BinaryWriter`, `BinaryReader`, `TCPClient` and so on, are no longer part of TLSharp itself.
|
The files without the previously mentioned notice are no longer part of TLSharp itself, or have enough modifications
|
||||||
|
to make them entirely different.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
This project requires the following Python modules, which can be installed by issuing `sudo -H pip install <module>` on a Linux terminal:
|
This project requires the following Python modules, which can be installed by issuing `sudo -H pip install <module>` on a
|
||||||
|
Linux terminal:
|
||||||
- `pyaes` ([GitHub](https://github.com/ricmoo/pyaes), [package index](https://pypi.python.org/pypi/pyaes))
|
- `pyaes` ([GitHub](https://github.com/ricmoo/pyaes), [package index](https://pypi.python.org/pypi/pyaes))
|
||||||
|
|
||||||
### We need your help!
|
### We need your help!
|
||||||
As of now, the project is fully **untested** and with many pending things to do. If you know both Python and C#, please don't think it twice and help us (me)!
|
As of now, the project is fully **untested** and with many pending things to do. If you know both Python and C#, please don't
|
||||||
|
think it twice and help us (me)!
|
||||||
|
|
||||||
### Code generator limitations
|
### Code generator limitations
|
||||||
The current code generator is not complete, yet adding the missing features would only over-complicate an already hard-to-read code.
|
The current code generator is not complete, yet adding the missing features would only over-complicate an already hard-to-read code.
|
||||||
Some parts of the .tl file _should_ be omitted, because they're "built-in" in the generated code (such as writing booleans, etc.).
|
Some parts of the `.tl` file _should_ be omitted, because they're "built-in" in the generated code (such as writing booleans, etc.).
|
||||||
|
|
||||||
In order to make sure that all the generated files will work, please make sure to **always** comment out these lines in `scheme.tl`
|
In order to make sure that all the generated files will work, please make sure to **always** comment out these lines in `scheme.tl`
|
||||||
(the latest version can always be found [here](https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/scheme.tl)):
|
(the latest version can always be found
|
||||||
|
[here](https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/scheme.tl)):
|
||||||
|
|
||||||
```tl
|
```tl
|
||||||
// boolFalse#bc799737 = Bool;
|
// boolFalse#bc799737 = Bool;
|
||||||
|
@ -24,6 +30,5 @@ In order to make sure that all the generated files will work, please make sure t
|
||||||
// vector#1cb5c415 {t:Type} # [ t ] = Vector t;
|
// vector#1cb5c415 {t:Type} # [ t ] = Vector t;
|
||||||
```
|
```
|
||||||
|
|
||||||
Also please make sure to rename `updates#74ae4240 ...` to `updates_tg#74ae4240 ...` or similar to avoid confusion between the updates folder and the updates.py file!
|
Also please make sure to rename `updates#74ae4240 ...` to `updates_tg#74ae4240 ...` or similar to avoid confusion between
|
||||||
|
the `updates` folder and the `updates.py` file!
|
||||||
|
|
||||||
|
|
12
main.py
12
main.py
|
@ -1,7 +1,9 @@
|
||||||
import tl.generator
|
import parser.tl_generator
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if not tl.generator.tlobjects_exist():
|
if not parser.tl_generator.tlobjects_exist():
|
||||||
print('Please run tl/generator.py at least once before continuing')
|
print('First run. Generating TLObjects...')
|
||||||
else:
|
parser.tl_generator.generate_tlobjects('scheme.tl')
|
||||||
pass
|
print('Done.')
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
# This file is based on TLSharp
|
||||||
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Network/MtProtoPlainSender.cs
|
||||||
import time
|
import time
|
||||||
from utils.binary_writer import BinaryWriter
|
from utils.binary_writer import BinaryWriter
|
||||||
from utils.binary_reader import BinaryReader
|
from utils.binary_reader import BinaryReader
|
||||||
|
|
||||||
|
|
||||||
class MtProtoPlainSender:
|
class MtProtoPlainSender:
|
||||||
|
"""MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)"""
|
||||||
def __init__(self, transport):
|
def __init__(self, transport):
|
||||||
self._sequence = 0
|
self._sequence = 0
|
||||||
self._time_offset = 0
|
self._time_offset = 0
|
||||||
|
@ -13,6 +14,7 @@ class MtProtoPlainSender:
|
||||||
self._transport = transport
|
self._transport = transport
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
|
"""Sends a plain packet (auth_key_id = 0) containing the given message body (data)"""
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
writer.write_long(0)
|
writer.write_long(0)
|
||||||
writer.write_int(self.get_new_msg_id())
|
writer.write_int(self.get_new_msg_id())
|
||||||
|
@ -23,18 +25,21 @@ class MtProtoPlainSender:
|
||||||
self._transport.send(packet)
|
self._transport.send(packet)
|
||||||
|
|
||||||
def receive(self):
|
def receive(self):
|
||||||
|
"""Receives a plain packet, returning the body of the response"""
|
||||||
result = self._transport.receive()
|
result = self._transport.receive()
|
||||||
with BinaryReader(result.body) as reader:
|
with BinaryReader(result.body) as reader:
|
||||||
auth_key_id = reader.read_long()
|
auth_key_id = reader.read_long()
|
||||||
message_id = reader.read_long()
|
msg_id = reader.read_long()
|
||||||
message_length = reader.read_int()
|
message_length = reader.read_int()
|
||||||
|
|
||||||
response = reader.read(message_length)
|
response = reader.read(message_length)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_new_msg_id(self):
|
def get_new_msg_id(self):
|
||||||
new_msg_id = int(self._time_offset + time.time() * 1000) # multiply by 1000 to get milliseconds
|
"""Generates a new message ID based on the current time (in ms) since epoch"""
|
||||||
|
new_msg_id = int(self._time_offset + time.time() * 1000) # Multiply by 1000 to get milliseconds
|
||||||
|
|
||||||
|
# Ensure that we always return a message ID which is higher than the previous one
|
||||||
if self._last_msg_id >= new_msg_id:
|
if self._last_msg_id >= new_msg_id:
|
||||||
new_msg_id = self._last_msg_id + 4
|
new_msg_id = self._last_msg_id + 4
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# This file is based on TLSharp
|
||||||
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Network/MtProtoSender.cs
|
||||||
import re
|
import re
|
||||||
import zlib
|
import zlib
|
||||||
import pyaes
|
import pyaes
|
||||||
|
@ -5,51 +7,68 @@ from time import sleep
|
||||||
|
|
||||||
from utils.binary_writer import BinaryWriter
|
from utils.binary_writer import BinaryWriter
|
||||||
from utils.binary_reader import BinaryReader
|
from utils.binary_reader import BinaryReader
|
||||||
from requests.ack_request import AckRequest
|
from tl.types.msgs_ack import MsgsAck
|
||||||
import utils.helpers as helpers
|
import utils.helpers as helpers
|
||||||
|
|
||||||
|
|
||||||
class MtProtoSender:
|
class MtProtoSender:
|
||||||
|
"""MTProto Mobile Protocol sender (https://core.telegram.org/mtproto/description)"""
|
||||||
def __init__(self, transport, session):
|
def __init__(self, transport, session):
|
||||||
self._transport = transport
|
self.transport = transport
|
||||||
self._session = session
|
self.session = session
|
||||||
|
self.need_confirmation = [] # Message IDs that need confirmation
|
||||||
self.need_confirmation = []
|
|
||||||
|
|
||||||
def change_transport(self, transport):
|
|
||||||
self._transport = transport
|
|
||||||
|
|
||||||
def generate_sequence(self, confirmed):
|
def generate_sequence(self, confirmed):
|
||||||
|
"""Generates the next sequence number, based on whether it was confirmed yet or not"""
|
||||||
if confirmed:
|
if confirmed:
|
||||||
result = self._session.sequence * 2 + 1
|
result = self.session.sequence * 2 + 1
|
||||||
self._session.sequence += 1
|
self.session.sequence += 1
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
return self._session.sequence * 2
|
return self.session.sequence * 2
|
||||||
|
|
||||||
# TODO async?
|
# region Send and receive
|
||||||
|
|
||||||
|
# TODO In TLSharp, this was async. Should this be?
|
||||||
def send(self, request):
|
def send(self, request):
|
||||||
if self.need_confirmation:
|
"""Sends the specified MTProtoRequest, previously sending any message which needed confirmation"""
|
||||||
ack_request = AckRequest(self.need_confirmation)
|
|
||||||
|
|
||||||
|
# First check if any message needs confirmation, if this is the case, send an "AckRequest"
|
||||||
|
if self.need_confirmation:
|
||||||
|
msgs_ack = MsgsAck(self.need_confirmation)
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
ack_request.on_send(writer)
|
msgs_ack.on_send(writer)
|
||||||
self.send_packet(writer.get_bytes(), ack_request)
|
self.send_packet(writer.get_bytes(), msgs_ack)
|
||||||
del self.need_confirmation[:]
|
del self.need_confirmation[:]
|
||||||
|
|
||||||
|
# Then send our packed request
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
request.on_send(writer)
|
request.on_send(writer)
|
||||||
self.send_packet(writer.get_bytes(), request)
|
self.send_packet(writer.get_bytes(), request)
|
||||||
|
|
||||||
self._session.save()
|
# And update the saved session
|
||||||
|
self.session.save()
|
||||||
|
|
||||||
|
def receive(self, request):
|
||||||
|
"""Receives the specified MTProtoRequest ("fills in it" the received data)"""
|
||||||
|
while not request.confirm_received:
|
||||||
|
message, remote_msg_id, remote_sequence = self.decode_msg(self.transport.receive().body)
|
||||||
|
|
||||||
|
with BinaryReader(message) as reader:
|
||||||
|
self.process_msg(remote_msg_id, remote_sequence, reader, request)
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Low level processing
|
||||||
|
|
||||||
def send_packet(self, packet, request):
|
def send_packet(self, packet, request):
|
||||||
request.message_id = self._session.get_new_msg_id()
|
"""Sends the given packet bytes with the additional information of the original request"""
|
||||||
|
request.msg_id = self.session.get_new_msg_id()
|
||||||
|
|
||||||
|
# First calculate the ciphered bit
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
# TODO Is there any difference with unsigned long and long?
|
writer.write_long(self.session.salt, signed=False)
|
||||||
writer.write_long(self._session.salt, signed=False)
|
writer.write_long(self.session.id, signed=False)
|
||||||
writer.write_long(self._session.id, signed=False)
|
|
||||||
writer.write_long(request.msg_id)
|
writer.write_long(request.msg_id)
|
||||||
writer.write_int(self.generate_sequence(request.confirmed))
|
writer.write_int(self.generate_sequence(request.confirmed))
|
||||||
writer.write_int(len(packet))
|
writer.write_int(len(packet))
|
||||||
|
@ -57,21 +76,22 @@ class MtProtoSender:
|
||||||
|
|
||||||
msg_key = helpers.calc_msg_key(writer.get_bytes())
|
msg_key = helpers.calc_msg_key(writer.get_bytes())
|
||||||
|
|
||||||
key, iv = helpers.calc_key(self._session.auth_key.data, msg_key, True)
|
key, iv = helpers.calc_key(self.session.auth_key.data, msg_key, True)
|
||||||
aes = pyaes.AESModeOfOperationCFB(key, iv, 16)
|
aes = pyaes.AESModeOfOperationCFB(key, iv, 16)
|
||||||
cipher_text = aes.encrypt(writer.get_bytes())
|
cipher_text = aes.encrypt(writer.get_bytes())
|
||||||
|
|
||||||
|
# And then finally send the packet
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
# TODO is it unsigned long?
|
writer.write_long(self.session.auth_key.id, signed=False)
|
||||||
writer.write_long(self._session.auth_key.id, signed=False)
|
|
||||||
writer.write(msg_key)
|
writer.write(msg_key)
|
||||||
writer.write(cipher_text)
|
writer.write(cipher_text)
|
||||||
|
|
||||||
self._transport.send(writer.get_bytes())
|
self.transport.send(writer.get_bytes())
|
||||||
|
|
||||||
def decode_msg(self, body):
|
def decode_msg(self, body):
|
||||||
|
"""Decodes an received encrypted message body bytes"""
|
||||||
message = None
|
message = None
|
||||||
remote_message_id = None
|
remote_msg_id = None
|
||||||
remote_sequence = None
|
remote_sequence = None
|
||||||
|
|
||||||
with BinaryReader(body) as reader:
|
with BinaryReader(body) as reader:
|
||||||
|
@ -82,72 +102,70 @@ class MtProtoSender:
|
||||||
remote_auth_key_id = reader.read_long()
|
remote_auth_key_id = reader.read_long()
|
||||||
msg_key = reader.read(16)
|
msg_key = reader.read(16)
|
||||||
|
|
||||||
key, iv = helpers.calc_key(self._session.auth_key.data, msg_key, False)
|
key, iv = helpers.calc_key(self.session.auth_key.data, msg_key, False)
|
||||||
aes = pyaes.AESModeOfOperationCFB(key, iv, 16)
|
aes = pyaes.AESModeOfOperationCFB(key, iv, 16)
|
||||||
plain_text = aes.decrypt(reader.read(len(body) - reader.tell_position()))
|
plain_text = aes.decrypt(reader.read(len(body) - reader.tell_position()))
|
||||||
|
|
||||||
with BinaryReader(plain_text) as plain_text_reader:
|
with BinaryReader(plain_text) as plain_text_reader:
|
||||||
remote_salt = plain_text_reader.read_long()
|
remote_salt = plain_text_reader.read_long()
|
||||||
remote_session_id = plain_text_reader.read_long()
|
remote_session_id = plain_text_reader.read_long()
|
||||||
remote_message_id = plain_text_reader.read_long()
|
remote_msg_id = plain_text_reader.read_long()
|
||||||
remote_sequence = plain_text_reader.read_int()
|
remote_sequence = plain_text_reader.read_int()
|
||||||
msg_len = plain_text_reader.read_int()
|
msg_len = plain_text_reader.read_int()
|
||||||
message = plain_text_reader.read(msg_len)
|
message = plain_text_reader.read(msg_len)
|
||||||
|
|
||||||
return message, remote_message_id, remote_sequence
|
return message, remote_msg_id, remote_sequence
|
||||||
|
|
||||||
def receive(self, mtproto_request):
|
def process_msg(self, msg_id, sequence, reader, request):
|
||||||
while not mtproto_request.confirm_received:
|
"""Processes and handles a Telegram message"""
|
||||||
message, remote_message_id, remote_sequence = self.decode_msg(self._transport.receive().body)
|
|
||||||
|
|
||||||
with BinaryReader(message) as reader:
|
|
||||||
self.process_msg(remote_message_id, remote_sequence, reader, mtproto_request)
|
|
||||||
|
|
||||||
def process_msg(self, message_id, sequence, reader, mtproto_request):
|
|
||||||
# TODO Check salt, session_id and sequence_number
|
# TODO Check salt, session_id and sequence_number
|
||||||
self.need_confirmation.append(message_id)
|
self.need_confirmation.append(msg_id)
|
||||||
|
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
reader.seek(-4)
|
reader.seek(-4)
|
||||||
|
|
||||||
if code == 0x73f1f8dc: # Container
|
if code == 0x73f1f8dc: # Container
|
||||||
return self.handle_container(message_id, sequence, reader, mtproto_request)
|
return self.handle_container(msg_id, sequence, reader, request)
|
||||||
if code == 0x7abe77ec: # Ping
|
if code == 0x7abe77ec: # Ping
|
||||||
return self.handle_ping(message_id, sequence, reader)
|
return self.handle_ping(msg_id, sequence, reader)
|
||||||
if code == 0x347773c5: # pong
|
if code == 0x347773c5: # pong
|
||||||
return self.handle_pong(message_id, sequence, reader)
|
return self.handle_pong(msg_id, sequence, reader)
|
||||||
if code == 0xae500895: # future_salts
|
if code == 0xae500895: # future_salts
|
||||||
return self.handle_future_salts(message_id, sequence, reader)
|
return self.handle_future_salts(msg_id, sequence, reader)
|
||||||
if code == 0x9ec20908: # new_session_created
|
if code == 0x9ec20908: # new_session_created
|
||||||
return self.handle_new_session_created(message_id, sequence, reader)
|
return self.handle_new_session_created(msg_id, sequence, reader)
|
||||||
if code == 0x62d6b459: # msgs_ack
|
if code == 0x62d6b459: # msgs_ack
|
||||||
return self.handle_msgs_ack(message_id, sequence, reader)
|
return self.handle_msgs_ack(msg_id, sequence, reader)
|
||||||
if code == 0xedab447b: # bad_server_salt
|
if code == 0xedab447b: # bad_server_salt
|
||||||
return self.handle_bad_server_salt(message_id, sequence, reader, mtproto_request)
|
return self.handle_bad_server_salt(msg_id, sequence, reader, request)
|
||||||
if code == 0xa7eff811: # bad_msg_notification
|
if code == 0xa7eff811: # bad_msg_notification
|
||||||
return self.handle_bad_msg_notification(message_id, sequence, reader)
|
return self.handle_bad_msg_notification(msg_id, sequence, reader)
|
||||||
if code == 0x276d3ec6: # msg_detailed_info
|
if code == 0x276d3ec6: # msg_detailed_info
|
||||||
return self.hangle_msg_detailed_info(message_id, sequence, reader)
|
return self.hangle_msg_detailed_info(msg_id, sequence, reader)
|
||||||
if code == 0xf35c6d01: # rpc_result
|
if code == 0xf35c6d01: # rpc_result
|
||||||
return self.handle_rpc_result(message_id, sequence, reader, mtproto_request)
|
return self.handle_rpc_result(msg_id, sequence, reader, request)
|
||||||
if code == 0x3072cfa1: # gzip_packed
|
if code == 0x3072cfa1: # gzip_packed
|
||||||
return self.handle_gzip_packed(message_id, sequence, reader, mtproto_request)
|
return self.handle_gzip_packed(msg_id, sequence, reader, request)
|
||||||
|
|
||||||
if (code == 0xe317af7e or
|
if (code == 0xe317af7e or
|
||||||
code == 0xd3f45784 or
|
code == 0xd3f45784 or
|
||||||
code == 0x2b2fbd4e or
|
code == 0x2b2fbd4e or
|
||||||
code == 0x78d4dec1 or
|
code == 0x78d4dec1 or
|
||||||
code == 0x725b04c3 or
|
code == 0x725b04c3 or
|
||||||
code == 0x74ae4240):
|
code == 0x74ae4240):
|
||||||
return self.handle_update(message_id, sequence, reader)
|
return self.handle_update(msg_id, sequence, reader)
|
||||||
|
|
||||||
# TODO Log unknown message code
|
print('Unknown message: {}'.format(hex(msg_id)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_update(self, message_id, sequence, reader):
|
# endregion
|
||||||
|
|
||||||
|
# region Message handling
|
||||||
|
|
||||||
|
def handle_update(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_container(self, message_id, sequence, reader, mtproto_request):
|
def handle_container(self, msg_id, sequence, reader, request):
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
size = reader.read_int()
|
size = reader.read_int()
|
||||||
for _ in range(size):
|
for _ in range(size):
|
||||||
|
@ -156,7 +174,7 @@ class MtProtoSender:
|
||||||
inner_length = reader.read_int()
|
inner_length = reader.read_int()
|
||||||
begin_position = reader.tell_position()
|
begin_position = reader.tell_position()
|
||||||
try:
|
try:
|
||||||
if not self.process_msg(inner_msg_id, sequence, reader, mtproto_request):
|
if not self.process_msg(inner_msg_id, sequence, reader, request):
|
||||||
reader.set_position(begin_position + inner_length)
|
reader.set_position(begin_position + inner_length)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
@ -164,40 +182,40 @@ class MtProtoSender:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_ping(self, message_id, sequence, reader):
|
def handle_ping(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_pong(self, message_id, sequence, reader):
|
def handle_pong(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_future_salts(self, message_id, sequence, reader):
|
def handle_future_salts(self, msg_id, sequence, reader):
|
||||||
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)
|
||||||
reader.seek(-12)
|
reader.seek(-12)
|
||||||
|
|
||||||
raise NotImplementedError("Handle future server salts function isn't implemented.")
|
raise NotImplementedError("Handle future server salts function isn't implemented.")
|
||||||
|
|
||||||
def handle_new_session_created(self, message_id, sequence, reader):
|
def handle_new_session_created(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_msgs_ack(self, message_id, sequence, reader):
|
def handle_msgs_ack(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_bad_server_salt(self, message_id, sequence, reader, mtproto_request):
|
def handle_bad_server_salt(self, msg_id, sequence, reader, mtproto_request):
|
||||||
code = reader.read_int(signed=False)
|
code = reader.read_int(signed=False)
|
||||||
bad_msg_id = reader.read_long(signed=False)
|
bad_msg_id = reader.read_long(signed=False)
|
||||||
bad_msg_seq_no = reader.read_int()
|
bad_msg_seq_no = reader.read_int()
|
||||||
error_code = reader.read_int()
|
error_code = reader.read_int()
|
||||||
new_salt = reader.read_long(signed=False)
|
new_salt = reader.read_long(signed=False)
|
||||||
|
|
||||||
self._session.salt = new_salt
|
self.session.salt = new_salt
|
||||||
|
|
||||||
# Resend
|
# Resend
|
||||||
self.send(mtproto_request)
|
self.send(mtproto_request)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handle_bad_msg_notification(self, message_id, sequence, reader):
|
def handle_bad_msg_notification(self, msg_id, sequence, reader):
|
||||||
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)
|
||||||
request_sequence = reader.read_int()
|
request_sequence = reader.read_int()
|
||||||
|
@ -238,14 +256,14 @@ class MtProtoSender:
|
||||||
|
|
||||||
raise NotImplementedError('This should never happen!')
|
raise NotImplementedError('This should never happen!')
|
||||||
|
|
||||||
def hangle_msg_detailed_info(self, message_id, sequence, reader):
|
def hangle_msg_detailed_info(self, msg_id, sequence, reader):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_rpc_result(self, message_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)
|
||||||
|
|
||||||
if request_id == mtproto_request.message_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)
|
inner_code = reader.read_int(signed=False)
|
||||||
|
@ -273,7 +291,6 @@ class MtProtoSender:
|
||||||
|
|
||||||
with BinaryReader(unpacked_data) as compressed_reader:
|
with BinaryReader(unpacked_data) as compressed_reader:
|
||||||
mtproto_request.on_response(compressed_reader)
|
mtproto_request.on_response(compressed_reader)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -281,10 +298,13 @@ class MtProtoSender:
|
||||||
reader.seek(-4)
|
reader.seek(-4)
|
||||||
mtproto_request.on_response(reader)
|
mtproto_request.on_response(reader)
|
||||||
|
|
||||||
def handle_gzip_packed(self, message_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)
|
||||||
packed_data = reader.tgread_bytes()
|
packed_data = reader.tgread_bytes()
|
||||||
unpacked_data = zlib.decompress(packed_data)
|
unpacked_data = zlib.decompress(packed_data)
|
||||||
|
|
||||||
with BinaryReader(unpacked_data) as compressed_reader:
|
with BinaryReader(unpacked_data) as compressed_reader:
|
||||||
self.process_msg(message_id, sequence, compressed_reader, mtproto_request)
|
self.process_msg(msg_id, sequence, compressed_reader, mtproto_request)
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
pass
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
|
# Python rough implementation of a C# TCP client
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
|
||||||
class TcpClient:
|
class TcpClient:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
def connect(self, ip, port):
|
def connect(self, ip, port):
|
||||||
|
"""Connects to the specified IP and port number"""
|
||||||
self.socket.connect((ip, port))
|
self.socket.connect((ip, port))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""Closes the connection"""
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
|
"""Writes (sends) the specified bytes to the connected peer"""
|
||||||
self.socket.send(data)
|
self.socket.send(data)
|
||||||
|
|
||||||
def read(self, buffer_size):
|
def read(self, buffer_size):
|
||||||
|
"""Reads (receives) the specified bytes from the connected peer"""
|
||||||
self.socket.recv(buffer_size)
|
self.socket.recv(buffer_size)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
# This file is based on TLSharp
|
||||||
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Network/TcpMessage.cs
|
||||||
from zlib import crc32
|
from zlib import crc32
|
||||||
|
|
||||||
from utils.binary_writer import BinaryWriter
|
from utils.binary_writer import BinaryWriter
|
||||||
|
@ -6,7 +7,6 @@ from utils.binary_reader import BinaryReader
|
||||||
|
|
||||||
|
|
||||||
class TcpMessage:
|
class TcpMessage:
|
||||||
|
|
||||||
def __init__(self, seq_number, body):
|
def __init__(self, seq_number, body):
|
||||||
"""
|
"""
|
||||||
:param seq_number: Sequence number
|
:param seq_number: Sequence number
|
||||||
|
@ -19,6 +19,7 @@ class TcpMessage:
|
||||||
self.body = body
|
self.body = body
|
||||||
|
|
||||||
def encode(self):
|
def encode(self):
|
||||||
|
"""Returns the bytes of the this message encoded, following Telegram's guidelines"""
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
''' https://core.telegram.org/mtproto#tcp-transport
|
''' https://core.telegram.org/mtproto#tcp-transport
|
||||||
|
|
||||||
|
@ -38,7 +39,9 @@ class TcpMessage:
|
||||||
|
|
||||||
return writer.get_bytes()
|
return writer.get_bytes()
|
||||||
|
|
||||||
def decode(self, body):
|
@staticmethod
|
||||||
|
def decode(body):
|
||||||
|
"""Returns a TcpMessage from the given encoded bytes, decoding them previously"""
|
||||||
if body is None:
|
if body is None:
|
||||||
raise ValueError('body cannot be None')
|
raise ValueError('body cannot be None')
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
# This file is based on TLSharp
|
||||||
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Network/TcpTransport.cs
|
||||||
from zlib import crc32
|
from zlib import crc32
|
||||||
|
|
||||||
from network.tcp_message import TcpMessage
|
from network.tcp_message import TcpMessage
|
||||||
from network.tcp_client import TcpClient
|
from network.tcp_client import TcpClient
|
||||||
|
|
||||||
|
|
||||||
class TcpTransport:
|
class TcpTransport:
|
||||||
|
|
||||||
def __init__(self, ip_address, port):
|
def __init__(self, ip_address, port):
|
||||||
self._tcp_client = TcpClient()
|
self._tcp_client = TcpClient()
|
||||||
self._send_counter = 0
|
self._send_counter = 0
|
||||||
|
@ -13,20 +13,20 @@ class TcpTransport:
|
||||||
self._tcp_client.connect(ip_address, port)
|
self._tcp_client.connect(ip_address, port)
|
||||||
|
|
||||||
def send(self, packet):
|
def send(self, packet):
|
||||||
"""
|
"""Sends the given packet (bytes array) to the connected peer"""
|
||||||
:param packet: Bytes array representing the packet to be sent
|
|
||||||
"""
|
|
||||||
if not self._tcp_client.connected:
|
if not self._tcp_client.connected:
|
||||||
raise ConnectionError('Client not connected to server.')
|
raise ConnectionError('Client not connected to server.')
|
||||||
|
|
||||||
|
# Get a TcpMessage which contains the given packet
|
||||||
tcp_message = TcpMessage(self._send_counter, packet)
|
tcp_message = TcpMessage(self._send_counter, packet)
|
||||||
|
|
||||||
# TODO async? and receive too, of course
|
# 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
|
||||||
|
|
||||||
def receive(self):
|
def receive(self):
|
||||||
|
"""Receives a TcpMessage from the connected peer"""
|
||||||
|
|
||||||
# First read everything
|
# First read everything
|
||||||
packet_length_bytes = self._tcp_client.read(4)
|
packet_length_bytes = self._tcp_client.read(4)
|
||||||
packet_length = int.from_bytes(packet_length_bytes, byteorder='big')
|
packet_length = int.from_bytes(packet_length_bytes, byteorder='big')
|
||||||
|
@ -45,6 +45,7 @@ class TcpTransport:
|
||||||
if checksum != valid_checksum:
|
if checksum != valid_checksum:
|
||||||
raise ValueError('Invalid checksum, skip')
|
raise ValueError('Invalid checksum, skip')
|
||||||
|
|
||||||
|
# If we passed the tests, we can then return a valid TcpMessage
|
||||||
return TcpMessage(seq, body)
|
return TcpMessage(seq, body)
|
||||||
|
|
||||||
def dispose(self):
|
def dispose(self):
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
class SourceBuilder:
|
class SourceBuilder:
|
||||||
"""This class should be used to build .py source files"""
|
"""This class should be used to build .py source files"""
|
||||||
|
|
||||||
def __init__(self, out_stream=None, indent_size=4):
|
def __init__(self, out_stream, indent_size=4):
|
||||||
self.current_indent = 0
|
self.current_indent = 0
|
||||||
self.on_new_line = False
|
self.on_new_line = False
|
||||||
self.indent_size = indent_size
|
self.indent_size = indent_size
|
||||||
|
self.out_stream = out_stream
|
||||||
if out_stream is None:
|
|
||||||
self.out_stream = StringIO()
|
|
||||||
else:
|
|
||||||
self.out_stream = out_stream
|
|
||||||
|
|
||||||
def indent(self):
|
def indent(self):
|
||||||
|
"""Indents the current source code line by the current indentation level"""
|
||||||
self.write(' ' * (self.current_indent * self.indent_size))
|
self.write(' ' * (self.current_indent * self.indent_size))
|
||||||
|
|
||||||
def write(self, string):
|
def write(self, string):
|
||||||
|
"""Writes a string into the source code, applying indentation if required"""
|
||||||
if self.on_new_line:
|
if self.on_new_line:
|
||||||
self.on_new_line = False # We're not on a new line anymore
|
self.on_new_line = False # We're not on a new line anymore
|
||||||
if string.strip(): # If the string was not empty, indent; Else it probably was a new line
|
if string.strip(): # If the string was not empty, indent; Else it probably was a new line
|
||||||
|
@ -26,6 +21,7 @@ class SourceBuilder:
|
||||||
self.out_stream.write(string)
|
self.out_stream.write(string)
|
||||||
|
|
||||||
def writeln(self, string=''):
|
def writeln(self, string=''):
|
||||||
|
"""Writes a string into the source code _and_ appends a new line, applying indentation if required"""
|
||||||
self.write(string + '\n')
|
self.write(string + '\n')
|
||||||
self.on_new_line = True
|
self.on_new_line = True
|
||||||
|
|
||||||
|
@ -34,6 +30,7 @@ class SourceBuilder:
|
||||||
self.current_indent += 1
|
self.current_indent += 1
|
||||||
|
|
||||||
def end_block(self):
|
def end_block(self):
|
||||||
|
"""Ends an indentation block, leaving an empty line afterwards"""
|
||||||
self.current_indent -= 1
|
self.current_indent -= 1
|
||||||
self.writeln()
|
self.writeln()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from parser.tl_parser import TLParser
|
from parser.tl_parser import TLParser
|
||||||
from parser.source_builder import SourceBuilder
|
from parser.source_builder import SourceBuilder
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ def generate_tlobjects(scheme_file):
|
||||||
with open(filename, 'w', encoding='utf-8') as file:
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
# Let's build the source code!
|
# Let's build the source code!
|
||||||
with SourceBuilder(file) as builder:
|
with SourceBuilder(file) as builder:
|
||||||
builder.writeln('from requests.mtproto_request import MTProtoRequest')
|
builder.writeln('from tl.mtproto_request import MTProtoRequest')
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
builder.writeln('class {}(MTProtoRequest):'.format(get_class_name(tlobject)))
|
builder.writeln('class {}(MTProtoRequest):'.format(get_class_name(tlobject)))
|
||||||
|
@ -346,8 +347,3 @@ def write_onresponse_code(builder, arg, args, name=None):
|
||||||
|
|
||||||
if arg.is_flag:
|
if arg.is_flag:
|
||||||
builder.end_block()
|
builder.end_block()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
clean_tlobjects()
|
|
||||||
generate_tlobjects('scheme.tl')
|
|
|
@ -1,20 +0,0 @@
|
||||||
from requests.mtproto_request import MTProtoRequest
|
|
||||||
|
|
||||||
|
|
||||||
class AckRequest(MTProtoRequest):
|
|
||||||
def __init__(self, msgs):
|
|
||||||
super().__init__()
|
|
||||||
self.msgs = msgs
|
|
||||||
|
|
||||||
def on_send(self, writer):
|
|
||||||
writer.write_int(0x62d6b459) # msgs_ack
|
|
||||||
writer.write_int(0x1cb5c415) # vector
|
|
||||||
writer.write_int(len(self.msgs))
|
|
||||||
for msg_id in self.msgs:
|
|
||||||
writer.write_int(msg_id, signed=False)
|
|
||||||
|
|
||||||
def on_response(self, reader):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_exception(self, exception):
|
|
||||||
pass
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# This file is based on TLSharp
|
||||||
|
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Requests/MTProtoRequest.cs
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,23 @@ class BinaryReader:
|
||||||
# region Reading
|
# region Reading
|
||||||
|
|
||||||
def read_int(self, signed=True):
|
def read_int(self, signed=True):
|
||||||
|
"""Reads an integer (4 bytes) value"""
|
||||||
return int.from_bytes(self.reader.read(4), signed=signed, byteorder='big')
|
return int.from_bytes(self.reader.read(4), signed=signed, byteorder='big')
|
||||||
|
|
||||||
def read_long(self, signed=True):
|
def read_long(self, signed=True):
|
||||||
|
"""Reads a long integer (8 bytes) value"""
|
||||||
return int.from_bytes(self.reader.read(8), signed=signed, byteorder='big')
|
return int.from_bytes(self.reader.read(8), signed=signed, byteorder='big')
|
||||||
|
|
||||||
def read_large_int(self, bits):
|
def read_large_int(self, bits):
|
||||||
|
"""Reads a n-bits long integer value"""
|
||||||
return int.from_bytes(self.reader.read(bits // 8), byteorder='big')
|
return int.from_bytes(self.reader.read(bits // 8), byteorder='big')
|
||||||
|
|
||||||
def read(self, length):
|
def read(self, length):
|
||||||
|
"""Read the given amount of bytes"""
|
||||||
return self.reader.read(length)
|
return self.reader.read(length)
|
||||||
|
|
||||||
def get_bytes(self):
|
def get_bytes(self):
|
||||||
|
"""Gets the byte array representing the current buffer as a whole"""
|
||||||
return self.stream.getbuffer()
|
return self.stream.getbuffer()
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -40,6 +45,7 @@ class BinaryReader:
|
||||||
# region Telegram custom reading
|
# region Telegram custom reading
|
||||||
|
|
||||||
def tgread_bytes(self):
|
def tgread_bytes(self):
|
||||||
|
"""Reads a Telegram-encoded byte array, without the need of specifying its length"""
|
||||||
first_byte = self.read(1)
|
first_byte = self.read(1)
|
||||||
if first_byte == 254:
|
if first_byte == 254:
|
||||||
length = self.read(1) | (self.read(1) << 8) | (self.read(1) << 16)
|
length = self.read(1) | (self.read(1) << 8) | (self.read(1) << 16)
|
||||||
|
@ -56,6 +62,7 @@ class BinaryReader:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def tgread_string(self):
|
def tgread_string(self):
|
||||||
|
"""Reads a Telegram-encoded string"""
|
||||||
return str(self.tgread_bytes(), encoding='utf-8')
|
return str(self.tgread_bytes(), encoding='utf-8')
|
||||||
|
|
||||||
def tgread_object(self):
|
def tgread_object(self):
|
||||||
|
|
|
@ -18,9 +18,11 @@ class BinaryWriter:
|
||||||
# region Writing
|
# region Writing
|
||||||
|
|
||||||
def write_byte(self, value):
|
def write_byte(self, value):
|
||||||
|
"""Writes a single byte value"""
|
||||||
self.writer.write(pack('B', value))
|
self.writer.write(pack('B', value))
|
||||||
|
|
||||||
def write_int(self, value, signed=True):
|
def write_int(self, value, signed=True):
|
||||||
|
"""Writes an integer value (4 bytes), which can or cannot be signed"""
|
||||||
if signed:
|
if signed:
|
||||||
self.writer.write(pack('i', value))
|
self.writer.write(pack('i', value))
|
||||||
else:
|
else:
|
||||||
|
@ -28,6 +30,7 @@ class BinaryWriter:
|
||||||
self.writer.write(pack('I', value))
|
self.writer.write(pack('I', value))
|
||||||
|
|
||||||
def write_long(self, value, signed=True):
|
def write_long(self, value, signed=True):
|
||||||
|
"""Writes a long integer value (8 bytes), which can or cannot be signed"""
|
||||||
if signed:
|
if signed:
|
||||||
self.writer.write(pack('q', value))
|
self.writer.write(pack('q', value))
|
||||||
else:
|
else:
|
||||||
|
@ -35,15 +38,19 @@ class BinaryWriter:
|
||||||
self.writer.write(pack('Q', value))
|
self.writer.write(pack('Q', value))
|
||||||
|
|
||||||
def write_float(self, value):
|
def write_float(self, value):
|
||||||
|
"""Writes a floating point value (4 bytes)"""
|
||||||
self.writer.write(pack('f', value))
|
self.writer.write(pack('f', value))
|
||||||
|
|
||||||
def write_double(self, value):
|
def write_double(self, value):
|
||||||
|
"""Writes a floating point value (8 bytes)"""
|
||||||
self.writer.write(pack('d', value))
|
self.writer.write(pack('d', value))
|
||||||
|
|
||||||
def write_large_int(self, value, bits):
|
def write_large_int(self, value, bits):
|
||||||
|
"""Writes a n-bits long integer value"""
|
||||||
self.writer.write(pack('{}B'.format(bits // 8), value))
|
self.writer.write(pack('{}B'.format(bits // 8), value))
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
|
"""Writes the given bytes array"""
|
||||||
self.writer.write(data)
|
self.writer.write(data)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -51,7 +58,7 @@ class BinaryWriter:
|
||||||
# region Telegram custom writing
|
# region Telegram custom writing
|
||||||
|
|
||||||
def tgwrite_bytes(self, data):
|
def tgwrite_bytes(self, data):
|
||||||
|
"""Write bytes by using Telegram guidelines"""
|
||||||
if len(data) < 254:
|
if len(data) < 254:
|
||||||
padding = (len(data) + 1) % 4
|
padding = (len(data) + 1) % 4
|
||||||
if padding != 0:
|
if padding != 0:
|
||||||
|
@ -71,7 +78,6 @@ class BinaryWriter:
|
||||||
self.write(bytes([(len(data) >> 8) % 256]))
|
self.write(bytes([(len(data) >> 8) % 256]))
|
||||||
self.write(bytes([(len(data) >> 16) % 256]))
|
self.write(bytes([(len(data) >> 16) % 256]))
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
|
||||||
""" Original:
|
""" Original:
|
||||||
binaryWriter.Write((byte)254);
|
binaryWriter.Write((byte)254);
|
||||||
binaryWriter.Write((byte)(bytes.Length));
|
binaryWriter.Write((byte)(bytes.Length));
|
||||||
|
@ -82,22 +88,27 @@ class BinaryWriter:
|
||||||
self.write(bytes(padding))
|
self.write(bytes(padding))
|
||||||
|
|
||||||
def tgwrite_string(self, string):
|
def tgwrite_string(self, string):
|
||||||
|
"""Write a string by using Telegram guidelines"""
|
||||||
return self.tgwrite_bytes(string.encode('utf-8'))
|
return self.tgwrite_bytes(string.encode('utf-8'))
|
||||||
|
|
||||||
def tgwrite_bool(self, bool):
|
def tgwrite_bool(self, bool):
|
||||||
|
"""Write a boolean value by using Telegram guidelines"""
|
||||||
# boolTrue boolFalse
|
# boolTrue boolFalse
|
||||||
return self.write_int(0x997275b5 if bool else 0xbc799737, signed=False)
|
return self.write_int(0x997275b5 if bool else 0xbc799737, signed=False)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
"""Flush the current stream to "update" changes"""
|
||||||
self.writer.flush()
|
self.writer.flush()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""Close the current stream"""
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
# TODO Do I need to close the underlying stream?
|
# TODO Do I need to close the underlying stream?
|
||||||
|
|
||||||
def get_bytes(self, flush=True):
|
def get_bytes(self, flush=True):
|
||||||
|
"""Get the current bytes array content from the buffer, optionally flushing first"""
|
||||||
if flush:
|
if flush:
|
||||||
self.writer.flush()
|
self.writer.flush()
|
||||||
self.stream.getbuffer()
|
self.stream.getbuffer()
|
||||||
|
|
|
@ -4,6 +4,7 @@ from hashlib import sha1
|
||||||
|
|
||||||
|
|
||||||
def generate_random_long(signed=True):
|
def generate_random_long(signed=True):
|
||||||
|
"""Generates a random long integer (8 bytes), which is optionally signed"""
|
||||||
result = random.getrandbits(64)
|
result = random.getrandbits(64)
|
||||||
if not signed:
|
if not signed:
|
||||||
result &= 0xFFFFFFFFFFFFFFFF # Ensure it's unsigned
|
result &= 0xFFFFFFFFFFFFFFFF # Ensure it's unsigned
|
||||||
|
@ -12,6 +13,7 @@ def generate_random_long(signed=True):
|
||||||
|
|
||||||
|
|
||||||
def generate_random_bytes(count):
|
def generate_random_bytes(count):
|
||||||
|
"""Generates a random bytes array"""
|
||||||
with BinaryWriter() as writer:
|
with BinaryWriter() as writer:
|
||||||
for _ in range(count):
|
for _ in range(count):
|
||||||
writer.write(random.getrandbits(8))
|
writer.write(random.getrandbits(8))
|
||||||
|
@ -20,6 +22,7 @@ def generate_random_bytes(count):
|
||||||
|
|
||||||
|
|
||||||
def calc_key(shared_key, msg_key, client):
|
def calc_key(shared_key, msg_key, client):
|
||||||
|
"""Calculate the key based on Telegram guidelines, specifying whether it's the client or not"""
|
||||||
x = 0 if client else 8
|
x = 0 if client else 8
|
||||||
|
|
||||||
buffer = [0] * 48
|
buffer = [0] * 48
|
||||||
|
@ -47,10 +50,12 @@ def calc_key(shared_key, msg_key, client):
|
||||||
|
|
||||||
|
|
||||||
def calc_msg_key(data):
|
def calc_msg_key(data):
|
||||||
|
"""Calculates the message key from the given data"""
|
||||||
return sha1(data)[4:20]
|
return sha1(data)[4:20]
|
||||||
|
|
||||||
|
|
||||||
def calc_msg_key_offset(data, offset, limit):
|
def calc_msg_key_offset(data, offset, limit):
|
||||||
|
"""Calculates the message key from offset given data, with an optional offset and limit"""
|
||||||
# TODO untested, may not be offset like this
|
# TODO untested, may not be offset like this
|
||||||
# In the original code it was as parameters for the sha function, not slicing the array
|
# In the original code it was as parameters for the sha function, not slicing the array
|
||||||
return sha1(data[offset:offset + limit])[4:20]
|
return sha1(data[offset:offset + limit])[4:20]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user