Skip to content

Commit 28704b9

Browse files
committed
Add API compiler
1 parent 45ee7aa commit 28704b9

7 files changed

Lines changed: 1543 additions & 0 deletions

File tree

compiler/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Pyrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2017 Dan Tès <https://github.com/delivrance>
3+
#
4+
# This file is part of Pyrogram.
5+
#
6+
# Pyrogram is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published
8+
# by the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# Pyrogram is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.

compiler/api/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Pyrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2017 Dan Tès <https://github.com/delivrance>
3+
#
4+
# This file is part of Pyrogram.
5+
#
6+
# Pyrogram is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published
8+
# by the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# Pyrogram is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.

compiler/api/compiler.py

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# Pyrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2017 Dan Tès <https://github.com/delivrance>
3+
#
4+
# This file is part of Pyrogram.
5+
#
6+
# Pyrogram is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published
8+
# by the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# Pyrogram is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
18+
19+
import os
20+
import re
21+
import shutil
22+
23+
home = "compiler/api"
24+
dest = "pyrogram/api"
25+
notice_path = "NOTICE"
26+
27+
core_types = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool"]
28+
29+
30+
# TODO: Compiler was written in a rush. variables/methods name and pretty much all the code is fuzzy, but it works
31+
# TODO: Some constructors have flags:# but not flags.\d+\?
32+
33+
class Compiler:
34+
def __init__(self):
35+
self.section = "types" # TL Schema starts with types
36+
self.namespaces = {"types": set(), "functions": set()}
37+
self.objects = {}
38+
self.layer = None
39+
40+
self.schema = None
41+
42+
with open("{}/template/class.txt".format(home)) as f:
43+
self.template = f.read()
44+
45+
with open(notice_path) as f:
46+
notice = []
47+
48+
for line in f.readlines():
49+
notice.append("# {}".format(line).strip())
50+
51+
self.notice = "\n".join(notice)
52+
53+
def read_schema(self):
54+
"""Read schema files"""
55+
with open("{}/source/auth_key.tl".format(home)) as auth, \
56+
open("{}/source/sys_msgs.tl".format(home)) as system, \
57+
open("{}/source/main_api.tl".format(home)) as api:
58+
self.schema = auth.read() + system.read() + api.read()
59+
60+
def parse_schema(self):
61+
"""Parse schema line by line"""
62+
total = len(self.schema.splitlines())
63+
64+
for i, line in enumerate(self.schema.splitlines()):
65+
# Check for section changer lines
66+
section = re.match(r"---(\w+)---", line)
67+
if section:
68+
self.section = section.group(1)
69+
continue
70+
71+
# Save the layer version
72+
layer = re.match(r"//\sLAYER\s(\d+)", line)
73+
if layer:
74+
self.layer = layer.group(1)
75+
continue
76+
77+
combinator = re.match(r"^([\w.]+)#([0-9a-f]+)\s(.*)=\s([\w<>.]+);$", line, re.MULTILINE)
78+
79+
if combinator:
80+
name, id, args, type = combinator.groups()
81+
namespace, name = name.split(".") if "." in name else ("", name)
82+
args = re.findall(r"[^{](\w+):([\w?!.<>]+)", line)
83+
84+
print("Compiling APIs: [{}%]".format(round(i * 100 / total)), end="\r")
85+
86+
for i in args:
87+
if re.match(r"flags\.\d+\?", i[1]):
88+
has_flags = True
89+
break
90+
else:
91+
has_flags = False
92+
93+
if name == "updates":
94+
name = "update"
95+
96+
for i, item in enumerate(args):
97+
# if item[0] in keyword.kwlist + dir(builtins) + ["self"]:
98+
if item[0] == "self":
99+
args[i] = ("is_{}".format(item[0]), item[1])
100+
101+
if namespace:
102+
self.namespaces[self.section].add(namespace)
103+
104+
self.compile(namespace, name, id, args, has_flags)
105+
106+
self.objects[id] = "{}.{}{}.{}".format(
107+
self.section,
108+
"{}.".format(namespace) if namespace else "",
109+
self.snek(name),
110+
self.caml(name)
111+
)
112+
113+
def finish(self):
114+
with open("{}/all.py".format(dest), "w") as f:
115+
f.write(self.notice + "\n\n")
116+
f.write("layer = {}\n\n".format(self.layer))
117+
f.write("objects = {")
118+
119+
for k, v in self.objects.items():
120+
v = v.split(".")
121+
del v[-2]
122+
v = ".".join(v)
123+
124+
f.write("\n 0x{}: \"{}\",".format(k.zfill(8), v))
125+
126+
f.write("\n 0xbc799737: \"core.BoolFalse\",")
127+
f.write("\n 0x997275b5: \"core.BoolTrue\",")
128+
f.write("\n 0x1cb5c415: \"core.Vector\",")
129+
f.write("\n 0x73f1f8dc: \"core.MsgContainer\",")
130+
f.write("\n 0xae500895: \"core.FutureSalts\",")
131+
f.write("\n 0x0949d9dc: \"core.FutureSalt\",")
132+
f.write("\n 0x3072cfa1: \"core.GzipPacked\",")
133+
f.write("\n 0x5bb8e511: \"core.Message\"")
134+
135+
f.write("\n}\n")
136+
137+
for k, v in self.namespaces.items():
138+
with open("{}/{}/__init__.py".format(dest, k), "a") as f:
139+
f.write("from . import {}\n".format(", ".join([i for i in v])) if v else "")
140+
141+
@staticmethod
142+
def sort_args(args):
143+
"""Put flags at the end"""
144+
args = args.copy()
145+
flags = [i for i in args if re.match(r"flags\.\d+\?", i[1])]
146+
147+
for i in flags:
148+
args.remove(i)
149+
150+
return args + flags
151+
152+
def compile(self, namespace, name, id, args, has_flags):
153+
path = "{}/{}/{}".format(dest, self.section, namespace)
154+
os.makedirs(path, exist_ok=True)
155+
156+
init = "{}/__init__.py".format(path)
157+
158+
if not os.path.exists(init):
159+
with open(init, "w") as f:
160+
f.write(self.notice + "\n\n")
161+
162+
with open(init, "a") as f:
163+
f.write("from .{} import {}\n".format(self.snek(name), self.caml(name)))
164+
165+
sorted_args = self.sort_args(args)
166+
167+
object_id = "0x{}".format(id)
168+
169+
arguments = ", " + ", ".join(
170+
["{}{}".format(
171+
i[0],
172+
"=None" if re.match(r"flags\.\d+\?", i[1]) else ""
173+
) for i in sorted_args]
174+
) if args else ""
175+
176+
fields = "\n ".join(
177+
["self.{0} = {0} # {1}".format(i[0], i[1]) for i in args]
178+
) if args else "pass"
179+
180+
if has_flags:
181+
write_flags = []
182+
for i in args:
183+
flag = re.match(r"flags\.(\d+)\?", i[1])
184+
if flag:
185+
write_flags.append("flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0]))
186+
187+
write_flags = "\n ".join([
188+
"flags = 0",
189+
"\n ".join(write_flags),
190+
"b.write(Int(flags))"
191+
])
192+
else:
193+
write_flags = "# No flags"
194+
195+
read_flags = "flags = Int.read(b)" if has_flags else "# No flags"
196+
197+
write_types = read_types = ""
198+
199+
for arg_name, arg_type in args:
200+
flag = re.findall(r"flags\.(\d+)\?([\w<>.]+)", arg_type)
201+
202+
if flag:
203+
index, flag_type = flag[0]
204+
205+
if flag_type == "true":
206+
read_types += "\n "
207+
read_types += "{} = True if flags & (1 << {}) else False".format(arg_name, index)
208+
elif flag_type in core_types:
209+
write_types += "\n "
210+
write_types += "if self.{} is not None:\n ".format(arg_name)
211+
write_types += "b.write({}(self.{}))\n ".format(flag_type.title(), arg_name)
212+
213+
read_types += "\n "
214+
read_types += "{} = {}.read(b) if flags & (1 << {}) else None".format(
215+
arg_name, flag_type.title(), index
216+
)
217+
elif "vector" in flag_type.lower():
218+
sub_type = arg_type.split("<")[1][:-1]
219+
220+
write_types += "\n "
221+
write_types += "if self.{} is not None:\n ".format(arg_name)
222+
write_types += "b.write(Vector(self.{}{}))\n ".format(
223+
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
224+
)
225+
226+
read_types += "\n "
227+
read_types += "{} = Object.read(b{}) if flags & (1 << {}) else []\n ".format(
228+
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "", index
229+
)
230+
else:
231+
write_types += "\n "
232+
write_types += "if self.{} is not None:\n ".format(arg_name)
233+
write_types += "b.write(self.{}.write())\n ".format(arg_name)
234+
235+
read_types += "\n "
236+
read_types += "{} = Object.read(b) if flags & (1 << {}) else None\n ".format(
237+
arg_name, index
238+
)
239+
else:
240+
if arg_type in core_types:
241+
write_types += "\n "
242+
write_types += "b.write({}(self.{}))\n ".format(arg_type.title(), arg_name)
243+
244+
read_types += "\n "
245+
read_types += "{} = {}.read(b)\n ".format(arg_name, arg_type.title())
246+
elif "vector" in arg_type.lower():
247+
sub_type = arg_type.split("<")[1][:-1]
248+
249+
write_types += "\n "
250+
write_types += "b.write(Vector(self.{}{}))\n ".format(
251+
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
252+
)
253+
254+
read_types += "\n "
255+
read_types += "{} = Object.read(b{})\n ".format(
256+
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
257+
)
258+
else:
259+
write_types += "\n "
260+
write_types += "b.write(self.{}.write())\n ".format(arg_name)
261+
262+
read_types += "\n "
263+
read_types += "{} = Object.read(b)\n ".format(arg_name)
264+
265+
with open("{}/{}.py".format(path, self.snek(name)), "w") as f:
266+
f.write(
267+
self.template.format(
268+
notice=self.notice,
269+
class_name=self.caml(name),
270+
object_id=object_id,
271+
arguments=arguments,
272+
fields=fields,
273+
read_flags=read_flags,
274+
read_types=read_types,
275+
write_flags=write_flags,
276+
write_types=write_types,
277+
return_arguments=", ".join([i[0] for i in sorted_args])
278+
)
279+
)
280+
281+
@staticmethod
282+
def snek(s):
283+
# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
284+
s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s)
285+
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s).lower()
286+
287+
@staticmethod
288+
def caml(s):
289+
s = Compiler.snek(s).split("_")
290+
return "".join([str(i.title()) for i in s])
291+
292+
def start(self):
293+
shutil.rmtree("{}/types".format(dest), ignore_errors=True)
294+
shutil.rmtree("{}/functions".format(dest), ignore_errors=True)
295+
296+
self.read_schema()
297+
self.parse_schema()
298+
self.finish()
299+
300+
print()
301+
302+
303+
def start():
304+
c = Compiler()
305+
c.start()
306+
307+
308+
if "__main__" == __name__:
309+
home = "."
310+
dest = "../../pyrogram/api"
311+
notice_path = "../../NOTICE"
312+
313+
start()

compiler/api/source/auth_key.tl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
///////////////////////////////
2+
/// Authorization key creation
3+
///////////////////////////////
4+
5+
---types---
6+
7+
resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector<long> = ResPQ;
8+
9+
p_q_inner_data#83c95aec pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
10+
p_q_inner_data_temp#3c6a84d4 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
11+
12+
bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner;
13+
14+
server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
15+
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:bytes = Server_DH_Params;
16+
17+
server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:bytes g_a:bytes server_time:int = Server_DH_inner_data;
18+
19+
client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:bytes = Client_DH_Inner_Data;
20+
21+
dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
22+
dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
23+
dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
24+
25+
destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
26+
destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
27+
destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
28+
29+
---functions---
30+
31+
req_pq#60469778 nonce:int128 = ResPQ;
32+
33+
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params;
34+
35+
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;
36+
37+
destroy_auth_key#d1435160 = DestroyAuthKeyRes;

0 commit comments

Comments
 (0)