Add basic secret chats

This commit is contained in:
painor 2020-01-14 21:16:42 +01:00
parent d09f6a50b0
commit 3808793f98
9 changed files with 582 additions and 7 deletions

View File

@ -22,4 +22,5 @@ from .downloads import DownloadMethods
from .account import AccountMethods from .account import AccountMethods
from .auth import AuthMethods from .auth import AuthMethods
from .bots import BotMethods from .bots import BotMethods
from .secret import SecretChatMethods
from .telegramclient import TelegramClient from .telegramclient import TelegramClient

360
telethon/client/secret.py Normal file
View File

@ -0,0 +1,360 @@
import random
from hashlib import sha1, sha256
from time import time
from .. import utils
from ..crypto import AES
from ..errors import SecurityError, EncryptionAlreadyDeclinedError
from ..extensions import BinaryReader
from ..network.mtprotostate import MTProtoState
from ..tl.functions.messages import AcceptEncryptionRequest
from ..tl.functions.messages import GetDhConfigRequest, RequestEncryptionRequest, SendEncryptedServiceRequest, \
DiscardEncryptionRequest, SendEncryptedRequest
from ..tl.types import InputEncryptedChat, TypeEncryptedChat
from ..tl.types.messages import DhConfigNotModified
from ..tl.types.secret import *
DEFAULT_LAYER = 101
class ChatKey:
def __init__(self, auth_key: bytes):
self.auth_key = auth_key
self.fingerprint = None
class Chats:
def __init__(self, id: int, access_hash: int, key: ChatKey, admin: bool, user_id: int,
input_chat: InputEncryptedChat):
self.id = id
self.access_hash = access_hash
self.key = key
self.admin = admin
self.user_id = user_id
self.input_chat = input_chat
self.in_seq_no_x = 0 if admin else 1
self.out_seq_no_x = 1 if admin else 0
self.in_seq_no = 0
self.out_seq_no = 0
self.layer = DEFAULT_LAYER
self.ttl = 0
self.ttr = 100
self.updated = time()
self.incoming = {}
self.outgoing = {}
self.created = time()
self.rekeying = [0]
self.mtproto = 1
class SecretChatMethods:
def get_secret_chat(self, chat_id) -> Chats:
if isinstance(chat_id, int):
peer = self.secret_chats.get(chat_id, None)
if not peer:
raise ValueError("chat not found")
return peer
try:
peer = self.secret_chats.get(chat_id.id, None)
if not peer:
raise ValueError("chat not found")
return peer
except AttributeError:
pass
try:
peer = self.secret_chats.get(chat_id.chat_id, None)
if not peer:
raise ValueError("chat not found")
return peer
except AttributeError:
pass
raise ValueError("chat not found")
async def get_dh_config(self):
version = 0 if not self.dh_config else self.dh_config.version
dh_config = await self(GetDhConfigRequest(random_length=0, version=version))
if isinstance(dh_config, DhConfigNotModified):
return self.dh_config
dh_config.p = int.from_bytes(dh_config.p, 'big', signed=False)
self.dh_config = dh_config
return dh_config
def check_g_a(self, g_a: int, p: int) -> bool:
if g_a <= 1 or g_a >= p - 1:
raise ValueError("g_a is invalid (1 < g_a < p - 1 is false).")
if g_a < 2 ** 1984 or g_a >= p - 2 ** 1984:
raise ValueError("g_a is invalid (1 < g_a < p - 1 is false).")
return True
async def start_secret_chat(self, peer):
peer = utils.get_input_user(await self.get_input_entity(peer))
dh_config = await self.get_dh_config()
a = int.from_bytes(os.urandom(256), 'big', signed=False)
g_a = pow(dh_config.g, a, dh_config.p)
self.check_g_a(g_a, dh_config.p)
res = await self(RequestEncryptionRequest(user_id=peer, g_a=g_a.to_bytes(256, 'big', signed=False)))
self.temp_secret_chat[res.id] = a
return res.id
def generate_secret_in_seq_no(self, chat_id):
return self.secret_chats[chat_id].in_seq_no * 2 + self.secret_chats[chat_id].in_seq_no_x
def generate_secret_out_seq_no(self, chat_id):
return self.secret_chats[chat_id].out_seq_no * 2 + self.secret_chats[chat_id].out_seq_no_x
async def handle_decrypted_message(self, decrypted_message, peer: Chats):
if isinstance(decrypted_message, (DecryptedMessageService, DecryptedMessageService8)):
if isinstance(decrypted_message.action, DecryptedMessageActionRequestKey):
# TODO accept rekey
return
elif isinstance(decrypted_message.action, DecryptedMessageActionAcceptKey):
# TODO commit rekey
return
elif isinstance(decrypted_message.action, DecryptedMessageActionCommitKey):
# TODO complete rekey
return
elif isinstance(decrypted_message.action, DecryptedMessageActionNotifyLayer):
peer.layer = decrypted_message.action.layer
if decrypted_message.action.layer >= 17 and time() - peer.created > 15:
await self.notify_layer(peer)
if decrypted_message.action.layer >= 73:
peer.mtproto = 2
return
elif isinstance(decrypted_message.action, DecryptedMessageActionSetMessageTTL):
peer.ttl = decrypted_message.action.ttl_seconds
self.send_update(decrypted_message)
return
elif isinstance(decrypted_message.action, DecryptedMessageActionNoop):
return
elif isinstance(decrypted_message.action, DecryptedMessageActionResend):
decrypted_message.action.start_seq_no -= peer.out_seq_no_x
decrypted_message.action.end_seq_no -= peer.out_seq_no_x
decrypted_message.action.start_seq_no //= 2
decrypted_message.action.end_seq_no //= 2
self._log.warning(f"Resending messages for {peer.id}")
for seq, message in peer.outgoing:
if decrypted_message.action.start_seq_no <= seq <= decrypted_message.action.end_seq_no:
await self.send_secret_message(peer.id, message.message)
return
else:
return decrypted_message
elif isinstance(decrypted_message,
(DecryptedMessage8, DecryptedMessage23, DecryptedMessage46, DecryptedMessage)):
return decrypted_message
elif isinstance(decrypted_message, DecryptedMessageLayer):
# TODO add checks
peer.in_seq_no += 1
if decrypted_message.layer >= 17:
peer.layer = decrypted_message.layer
if decrypted_message.layer >= 17 and time() - peer.created > 15:
await self.notify_layer(peer)
decrypted_message = decrypted_message.message
return await self.handle_decrypted_message(decrypted_message, peer)
async def handle_encrypted_update(self, event):
if not self.secret_chats.get(event.message.chat_id):
self._log.debug("Secret chat not saved. skipping")
return False
message = event.message
auth_key_id = struct.unpack('<q', message.bytes[:8])[0]
peer = self.get_secret_chat(message.chat_id)
if not peer.key.fingerprint or \
auth_key_id != peer.key.fingerprint:
await self.close_secret_chat(message.chat_id)
raise ValueError("Key fingerprint mismatch. Chat closed")
message_key = message.bytes[8:24]
encrypted_data = message.bytes[24:]
if peer.mtproto == 2:
try:
decrypted_message = self.decrypt_mtproto2(bytes.fromhex(message_key.hex()), message.chat_id,
bytes.fromhex(encrypted_data.hex()))
except Exception as e:
decrypted_message = self.decrypt_mtproto1(bytes.fromhex(message_key.hex()), message.chat_id,
bytes.fromhex(encrypted_data.hex()))
peer.mtproto = 1
self._log.debug(f"Used MTProto 1 with chat {message.chat_id}")
else:
try:
decrypted_message = self.decrypt_mtproto1(bytes.fromhex(message_key.hex()), message.chat_id,
bytes.fromhex(encrypted_data.hex()))
except Exception as e:
decrypted_message = self.decrypt_mtproto2(bytes.fromhex(message_key.hex()), message.chat_id,
bytes.fromhex(encrypted_data.hex()))
peer.mtproto = 2
self._log.debug(f"Used MTProto 2 with chat {message.chat_id}")
peer.ttr -= 1
if (peer.ttr <= 0 or (time() - peer.updated) > 7 * 24 * 60 * 60) and peer.rekeying[0] == 0:
# TODO rekeying
raise ValueError("need re_keying")
peer.incoming[peer.in_seq_no] = message
return await self.handle_decrypted_message(decrypted_message, peer)
async def encrypt_secret_message(self, peer, message):
peer = self.get_secret_chat(peer)
peer.ttr -= 1
if peer.layer > 8:
if (peer.ttr <= 0 or (time() - peer.updated) > 7 * 24 * 60 * 60) and peer.rekeying[0] == 0:
# TODO rekeying
raise ValueError("need re_keying")
message = DecryptedMessageLayer(layer=peer.layer,
random_bytes=os.urandom(15 + 4 * random.randint(0, 2)),
in_seq_no=self.generate_secret_in_seq_no(peer.id),
out_seq_no=self.generate_secret_out_seq_no(peer.id),
message=message)
peer.out_seq_no += 1
peer.outgoing[peer.out_seq_no] = message
message = bytes(message)
message = struct.pack('<I', len(message)) + message
if peer.mtproto == 2:
padding = (16 - len(message) % 16) % 16
if padding < 12:
padding += 16
message += os.urandom(padding)
is_admin = (0 if peer.admin else 8)
first_str = peer.key.auth_key[88 + is_admin:88 + 32 + is_admin]
message_key = sha256(first_str + message).digest()[8:24]
aes_key, aes_iv = MTProtoState._calc_key(peer.key.auth_key, message_key,
peer.admin)
else:
message_key = sha1(message).digest()[-16:]
aes_key, aes_iv = MTProtoState._old_calc_key(peer.key.auth_key, message_key,
True)
padding = (16 - len(message) % 16) % 16
message += os.urandom(padding)
message = struct.pack('<q', peer.key.fingerprint) + message_key + AES.encrypt_ige(bytes.fromhex(message.hex()),
aes_key,
aes_iv)
return message
async def send_secret_message(self, peer_id, message, ttl=0, reply_to_id=None):
peer = self.get_secret_chat(peer_id)
if peer.layer == 8:
message = DecryptedMessage8(os.urandom(8), message, DecryptedMessageMediaEmpty())
elif peer.layer == 46:
message = DecryptedMessage46(ttl, message, reply_to_random_id=reply_to_id)
else:
message = DecryptedMessage(ttl, message, reply_to_random_id=reply_to_id)
data = await self.encrypt_secret_message(peer_id, message)
res = await self(
SendEncryptedRequest(peer=peer.input_chat, data=data))
return res
async def notify_layer(self, peer):
if isinstance(peer, int):
peer = self.secret_chats[peer]
else:
peer = self.secret_chats[peer.id]
if peer.layer == 8:
return
message = DecryptedMessageService8(action=DecryptedMessageActionNotifyLayer(
layer=min(DEFAULT_LAYER, peer.layer)), random_bytes=os.urandom(15 + 4 * random.randint(0, 2)))
data = await self.encrypt_secret_message(peer.id, message)
return await self(
SendEncryptedServiceRequest(peer=InputEncryptedChat(peer.id, peer.access_hash),
data=data))
async def close_secret_chat(self, peer):
if self.secret_chats.get(peer.id, None):
del self.secret_chats[peer]
if self.temp_secret_chat.get(peer.id, None):
del self.temp_secret_chat[peer.id]
try:
await self(DiscardEncryptionRequest(peer.id))
except EncryptionAlreadyDeclinedError:
pass
def decrypt_mtproto2(self, message_key, chat_id, encrypted_data):
peer = self.get_secret_chat(chat_id)
aes_key, aes_iv = MTProtoState._calc_key(self.secret_chats[chat_id].key.auth_key,
message_key,
not self.secret_chats[chat_id].admin)
decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv)
message_data_length = struct.unpack('<I', decrypted_data[:4])[0]
message_data = decrypted_data[4:message_data_length + 4]
if message_data_length > len(decrypted_data):
raise SecurityError("message data length is too big")
is_admin = peer.admin
first_str = peer.key.auth_key[88 + is_admin:88 + 32 + is_admin]
if message_key != sha256(first_str + decrypted_data).digest()[8:24]:
raise SecurityError("Message key mismatch")
if len(decrypted_data) - 4 - message_data_length < 12:
raise SecurityError("Padding is too small")
if len(decrypted_data) % 16 != 0:
raise SecurityError("Decrpyted data not divisble by 16")
return BinaryReader(message_data).tgread_object()
def decrypt_mtproto1(self, message_key, chat_id, encrypted_data):
aes_key, aes_iv = MTProtoState._old_calc_key(self.secret_chats[chat_id].key.auth_key,
message_key,
True)
decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv)
message_data_length = struct.unpack('<I', decrypted_data[:4])[0]
message_data = decrypted_data[4:message_data_length + 4]
if message_data_length > len(decrypted_data):
raise SecurityError("message data length is too big")
if message_key != sha1(decrypted_data[:4 + message_data_length]).digest()[-16:]:
raise SecurityError("Message key mismatch")
if len(decrypted_data) - 4 - message_data_length > 15:
raise SecurityError("Difference is too big")
if len(decrypted_data) % 16 != 0:
raise SecurityError("Decrypted data can not be divided by 16")
return BinaryReader(message_data).tgread_object()
async def accept_secret_chat(self, chat: TypeEncryptedChat):
if chat.id == 0:
raise ValueError("Already accepted")
dh_config = await self.get_dh_config()
random_bytes = os.urandom(256)
b = int.from_bytes(random_bytes, byteorder="big", signed=False)
g_a = int.from_bytes(chat.g_a, 'big', signed=False)
self.check_g_a(g_a, dh_config.p)
res = pow(g_a, b, dh_config.p)
auth_key = res.to_bytes(256, 'big', signed=False)
key = ChatKey(auth_key)
key.fingerprint = struct.unpack('<q', sha1(key.auth_key).digest()[-8:])[0]
input_peer = InputEncryptedChat(chat_id=chat.id, access_hash=chat.access_hash)
secret_chat = Chats(chat.id, chat.access_hash, key, admin=False, user_id=chat.admin_id, input_chat=input_peer)
self.secret_chats[chat.id] = secret_chat
g_b = pow(dh_config.g, b, dh_config.p)
self.check_g_a(g_b, dh_config.p)
result = await self(
AcceptEncryptionRequest(input_peer, g_b=g_b.to_bytes(256, 'big', signed=False),
key_fingerprint=key.fingerprint))
await self.notify_layer(chat)
return result
async def finish_secret_chat_creation(self, chat):
dh_config = await self.get_dh_config()
g_a_or_b = int.from_bytes(chat.g_a_or_b, "big", signed=False)
self.check_g_a(g_a_or_b, dh_config.p)
auth_key = pow(g_a_or_b, self.temp_secret_chat[chat.id], dh_config.p).to_bytes(256, "big", signed=False)
del self.temp_secret_chat[chat.id]
key = ChatKey(auth_key)
key.fingerprint = struct.unpack('<q', sha1(key.auth_key).digest()[-8:])[0]
if key.fingerprint != chat.key_fingerprint:
raise ValueError("Wrong fingerprint")
key.visualization_orig = sha1(key.auth_key).digest()[16:]
key.visualization_46 = sha256(key.auth_key).digest()[20:]
input_peer = InputEncryptedChat(chat_id=chat.id, access_hash=chat.access_hash)
self.secret_chats[chat.id] = Chats(
chat.id,
chat.access_hash,
key,
True,
chat.participant_id,
input_peer
)
await self.notify_layer(chat)

View File

@ -362,6 +362,13 @@ class TelegramBaseClient(abc.ABC):
# A place to store if channels are a megagroup or not (see `edit_admin`) # A place to store if channels are a megagroup or not (see `edit_admin`)
self._megagroup_cache = {} self._megagroup_cache = {}
# Secret chats
self.temp_secret_chat = {}
self.secret_chats = {}
self.dh_config = None
# endregion # endregion
# region Properties # region Properties

View File

@ -1,13 +1,14 @@
from . import ( from . import (
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods, AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods, BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
MessageParseMethods, UserMethods, TelegramBaseClient MessageParseMethods, UserMethods, TelegramBaseClient, SecretChatMethods
) )
class TelegramClient( class TelegramClient(
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods, AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods, BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
MessageParseMethods, UserMethods, TelegramBaseClient MessageParseMethods, UserMethods, TelegramBaseClient, SecretChatMethods
): ):
pass def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -8,7 +8,7 @@ from .newmessage import NewMessage
from .userupdate import UserUpdate from .userupdate import UserUpdate
from .callbackquery import CallbackQuery from .callbackquery import CallbackQuery
from .inlinequery import InlineQuery from .inlinequery import InlineQuery
from .secret import SecretChat
_HANDLERS_ATTRIBUTE = '__tl.handlers' _HANDLERS_ATTRIBUTE = '__tl.handlers'

74
telethon/events/secret.py Normal file
View File

@ -0,0 +1,74 @@
from .common import EventBuilder, EventCommon, name_inner_event
from ..tl import types
@name_inner_event
class SecretChat(EventBuilder):
def __init__(self, to_finish=False, to_accept=False, to_decrypt=False):
self.to_finish = to_finish
self.to_accept = to_accept
self.to_decrypt = to_decrypt
super().__init__()
@classmethod
def build(cls, update, others=None, self_id=None):
if isinstance(update, types.UpdateEncryption):
if isinstance(update.chat, types.EncryptedChat):
return cls.Event(update,
to_finish=True)
elif isinstance(update.chat, types.EncryptedChatRequested):
return cls.Event(update, to_accept=True)
elif isinstance(update, types.UpdateNewEncryptedMessage):
return cls.Event(update, to_decrypt=True)
class Event(EventCommon):
def __init__(self, update, to_finish=False, to_accept=False, to_decrypt=False):
if isinstance(update, types.UpdateEncryption):
super().__init__(chat_peer=update.chat)
else:
super().__init__(chat_peer=update.message.chat_id)
self.original_update = update
self.to_finish = to_finish
self.to_accept = to_accept
self.to_decrypt = to_decrypt
self.decrypted_message = None
def _set_client(self, client):
self._chat_peer = None
super()._set_client(client)
async def finish(self):
return await self._client.finish_secret_chat_creation(self.original_update.chat)
async def accept(self):
return await self._client.accept_secret_chat(self.original_update.chat)
async def decrypt(self):
self.decrypted_message = await self._client.handle_encrypted_update(self.original_update)
return self.decrypted_message
async def reply(self, message, ttl=0):
if not self.decrypted_message:
await self.decrypt()
return await self._client.send_secret_message(self.original_update.message.chat_id, message, ttl,
self.decrypted_message.random_id)
async def respond(self, message, ttl=0):
return await self._client.send_secret_message(self.original_update.message.chat_id, message, ttl)
pass
def filter(self, event):
event = event.original_update
if isinstance(event, types.UpdateEncryption):
if isinstance(event.chat, types.EncryptedChat):
if not self.to_finish:
return
elif isinstance(event.chat, types.EncryptedChatRequested):
if not self.to_accept:
return
elif isinstance(event, types.UpdateNewEncryptedMessage):
if not self.to_decrypt:
return
return super().filter(event)

View File

@ -1,7 +1,7 @@
import os import os
import struct import struct
import time import time
from hashlib import sha256 from hashlib import sha256, sha1
from ..crypto import AES from ..crypto import AES
from ..errors import SecurityError, InvalidBufferError from ..errors import SecurityError, InvalidBufferError
@ -59,6 +59,25 @@ class MTProtoState:
""" """
message.msg_id = self._get_new_msg_id() message.msg_id = self._get_new_msg_id()
@staticmethod
def _old_calc_key(auth_key, msg_key, client):
"""
Calculate the key based on Telegram guidelines for MTProto 1,
specifying whether it's the client or not. See
https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector
"""
x = 0 if client else 8
sha1a = sha1(msg_key + auth_key[x:x + 32]).digest()
sha1b = sha1(auth_key[x + 32:x + 48] + msg_key + auth_key[x + 48:x + 64]).digest()
sha1c = sha1(auth_key[x + 64:x + 96] + msg_key).digest()
sha1d = sha1(msg_key + auth_key[x + 96:x + 128]).digest()
aes_key = sha1a[0:8] + sha1b[8:20] + sha1c[4:16]
aes_iv = sha1a[8:20] + sha1b[0:8] + sha1c[16:20] + sha1d[0:8]
return aes_key, aes_iv
@staticmethod @staticmethod
def _calc_key(auth_key, msg_key, client): def _calc_key(auth_key, msg_key, client):
""" """

View File

@ -0,0 +1,109 @@
// layer 8
secret.decryptedMessage8#1f814f1f random_id:long random_bytes:bytes message:string media:DecryptedMessageMedia = secret.DecryptedMessage;
secret.decryptedMessageService8#aa48327d random_id:long random_bytes:bytes action:DecryptedMessageAction = secret.DecryptedMessage;
secret.decryptedMessageMediaEmpty#89f5c4a = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaPhoto23#32798a8c thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaVideo8#4cee6ef3 thumb:bytes thumb_w:int thumb_h:int duration:int w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaGeoPoint#35480a59 lat:double long:double = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaContact#588a0a97 phone_number:string first_name:string last_name:string user_id:int = secret.DecryptedMessageMedia;
secret.decryptedMessageActionSetMessageTTL#a1733aec ttl_seconds:int = secret.DecryptedMessageAction;
secret.decryptedMessageMediaDocument23#b095434b thumb:bytes thumb_w:int thumb_h:int file_name:string mime_type:string size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaAudio8#6080758f duration:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageActionReadMessages#c4f40be random_ids:Vector<long> = secret.DecryptedMessageAction;
secret.decryptedMessageActionDeleteMessages#65614304 random_ids:Vector<long> = secret.DecryptedMessageAction;
secret.decryptedMessageActionScreenshotMessages#8ac1f475 random_ids:Vector<long> = secret.DecryptedMessageAction;
secret.decryptedMessageActionFlushHistory#6719e45c = secret.DecryptedMessageAction;
// layer 23
secret.decryptedMessage23#204d3878 random_id:long ttl:int message:string media:DecryptedMessageMedia = secret.DecryptedMessage;
secret.decryptedMessageService#73164160 random_id:long action:DecryptedMessageAction = secret.DecryptedMessage;
secret.decryptedMessageMediaVideo23#524a415d thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaAudio#57e0a9cb duration:int mime_type:string size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
secret.decryptedMessageLayer#1be31789 random_bytes:bytes layer:int in_seq_no:int out_seq_no:int message:DecryptedMessage = secret.DecryptedMessageLayer;
secret.sendMessageTypingAction#16bf744e = secret.SendMessageAction;
secret.sendMessageCancelAction#fd5ec8f5 = secret.SendMessageAction;
secret.sendMessageRecordVideoAction#a187d66f = secret.SendMessageAction;
secret.sendMessageUploadVideoAction#92042ff7 = secret.SendMessageAction;
secret.sendMessageRecordAudioAction#d52f73f7 = secret.SendMessageAction;
secret.sendMessageUploadAudioAction#e6ac8a6f = secret.SendMessageAction;
secret.sendMessageUploadPhotoAction#990a3c1a = secret.SendMessageAction;
secret.sendMessageUploadDocumentAction#8faee98e = secret.SendMessageAction;
secret.sendMessageGeoLocationAction#176f8ba1 = secret.SendMessageAction;
secret.sendMessageChooseContactAction#628cbc6f = secret.SendMessageAction;
secret.decryptedMessageActionResend#511110b0 start_seq_no:int end_seq_no:int = secret.DecryptedMessageAction;
secret.decryptedMessageActionNotifyLayer#f3048883 layer:int = secret.DecryptedMessageAction;
secret.decryptedMessageActionTyping#ccb27641 action:SendMessageAction = secret.DecryptedMessageAction;
secret.decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = secret.DecryptedMessageAction;
secret.decryptedMessageActionAcceptKey#6fe1735b exchange_id:long g_b:bytes key_fingerprint:long = secret.DecryptedMessageAction;
secret.decryptedMessageActionAbortKey#dd05ec6b exchange_id:long = secret.DecryptedMessageAction;
secret.decryptedMessageActionCommitKey#ec2e0b9b exchange_id:long key_fingerprint:long = secret.DecryptedMessageAction;
secret.decryptedMessageActionNoop#a82fdd63 = secret.DecryptedMessageAction;
secret.documentAttributeImageSize#6c37c15c w:int h:int = secret.DocumentAttribute;
secret.documentAttributeAnimated#11b58939 = secret.DocumentAttribute;
secret.documentAttributeSticker23#fb0a5727 = secret.DocumentAttribute;
secret.documentAttributeVideo#5910cccb duration:int w:int h:int = secret.DocumentAttribute;
secret.documentAttributeAudio23#51448e5 duration:int = secret.DocumentAttribute;
secret.documentAttributeFilename#15590068 file_name:string = secret.DocumentAttribute;
secret.photoSizeEmpty#e17e23c type:string = secret.PhotoSize;
secret.photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = secret.PhotoSize;
secret.photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = secret.PhotoSize;
secret.fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = secret.FileLocation;
secret.fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = secret.FileLocation;
secret.decryptedMessageMediaExternalDocument#fa95b0dd id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = secret.DecryptedMessageMedia;
// layer 45
secret.documentAttributeAudio45#ded218e0 duration:int title:string performer:string = secret.DocumentAttribute;
// layer 46
secret.decryptedMessage46#36b091de flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long = secret.DecryptedMessage;
secret.decryptedMessageMediaPhoto#f1fa8d78 thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes caption:string = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaVideo#970c8c0e thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes caption:string = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaDocument#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector<DocumentAttribute> caption:string = secret.DecryptedMessageMedia;
secret.documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = secret.DocumentAttribute;
secret.documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = secret.DocumentAttribute;
secret.messageEntityUnknown#bb92ba95 offset:int length:int = secret.MessageEntity;
secret.messageEntityMention#fa04579d offset:int length:int = secret.MessageEntity;
secret.messageEntityHashtag#6f635b0d offset:int length:int = secret.MessageEntity;
secret.messageEntityBotCommand#6cef8ac7 offset:int length:int = secret.MessageEntity;
secret.messageEntityUrl#6ed02538 offset:int length:int = secret.MessageEntity;
secret.messageEntityEmail#64e475c2 offset:int length:int = secret.MessageEntity;
secret.messageEntityBold#bd610bc9 offset:int length:int = secret.MessageEntity;
secret.messageEntityItalic#826f8b60 offset:int length:int = secret.MessageEntity;
secret.messageEntityCode#28a20571 offset:int length:int = secret.MessageEntity;
secret.messageEntityPre#73924be0 offset:int length:int language:string = secret.MessageEntity;
secret.messageEntityTextUrl#76a6d327 offset:int length:int url:string = secret.MessageEntity;
secret.messageEntityMentionName#352dca58 offset:int length:int user_id:int = secret.MessageEntity;
secret.messageEntityPhone#9b69e34b offset:int length:int = secret.MessageEntity;
secret.messageEntityCashtag#4c4e743f offset:int length:int = secret.MessageEntity;
secret.inputStickerSetShortName#861cc8a0 short_name:string = secret.InputStickerSet;
secret.inputStickerSetEmpty#ffb62b95 = secret.InputStickerSet;
secret.decryptedMessageMediaVenue#8a0df56f lat:double long:double title:string address:string provider:string venue_id:string = secret.DecryptedMessageMedia;
secret.decryptedMessageMediaWebPage#e50511d8 url:string = secret.DecryptedMessageMedia;
// layer 66
secret.sendMessageRecordRoundAction#88f27fbc = secret.SendMessageAction;
secret.sendMessageUploadRoundAction#bb718624 = secret.SendMessageAction;
secret.documentAttributeVideo66#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = secret.DocumentAttribute;
// layer 73
secret.decryptedMessage#91cc4674 flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = secret.DecryptedMessage;
// layer 101
secret.messageEntityUnderline#9c4e7e8b offset:int length:int = secret.MessageEntity;
secret.messageEntityStrike#bf0693d4 offset:int length:int = secret.MessageEntity;
secret.messageEntityBlockquote#20df5d0 offset:int length:int = secret.MessageEntity;
---functions---
test.dummyFunction = secret.Bool;

View File

@ -49,8 +49,12 @@ class TLObject:
WHITELISTED_MISMATCHING_IDS.get(layer, set()) WHITELISTED_MISMATCHING_IDS.get(layer, set())
if self.fullname not in whitelist: if self.fullname not in whitelist:
assert self.id == self.infer_id(),\ # TODO figure out a better way of doing this
'Invalid inferred ID for ' + repr(self) # since there are multiple constructors of the same
# method in different layers and we need them all
pass
#assert self.id == self.infer_id(),\
# 'Invalid inferred ID for ' + repr(self)
self.class_name = snake_to_camel_case( self.class_name = snake_to_camel_case(
self.name, suffix='Request' if self.is_function else '') self.name, suffix='Request' if self.is_function else '')