2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-08-31 06:16:06 +00:00

Deep rewrite: preparing for v1.0

- Pyrogram core is now fully asynchronous
- Ditched Python 3.5, welcome 3.6 as minimum version.
- Moved all types to pyrogram.types
- Turned the Filters class into a module (filters)
- Moved all filters to pyrogram.filters
- Moved all handlers to pyrogram.handlers
- Moved all emoji to pyrogram.emoji
- Renamed pyrogram.api to pyrogram.raw
- Clock is now synced with server's time
- Telegram schema updated to Layer 117
- Greatly improved the TL compiler (proper type-constructor hierarchy)
- Added "do not edit" warning in generated files
- Crypto parts are executed in a thread pool to avoid blocking the event loop
- idle() is now a separate function (it doesn't deal with Client instances)
- Async storage, async filters and async progress callback (optional, can be sync too)
- Added getpass back, for hidden password inputs
This commit is contained in:
Dan
2020-08-22 08:05:05 +02:00
parent 2f0a1f4119
commit 538f1e3972
367 changed files with 12085 additions and 15090 deletions

View File

@@ -19,142 +19,110 @@
import os
import re
import shutil
from functools import partial
from pathlib import Path
from typing import NamedTuple, List, Tuple
HOME = "compiler/api"
DESTINATION = "pyrogram/api"
# from autoflake import fix_code
# from black import format_str, FileMode
HOME_PATH = Path("compiler/api")
DESTINATION_PATH = Path("pyrogram/raw")
NOTICE_PATH = "NOTICE"
SECTION_RE = re.compile(r"---(\w+)---")
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE)
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE)
ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)")
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
FLAGS_RE_3 = re.compile(r"flags:#")
INT_RE = re.compile(r"int(\d+)")
core_types = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool"]
CORE_TYPES = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool", "true"]
WARNING = """
# # # # # # # # # # # # # # # # # # # # # # # #
# !!! WARNING !!! #
# This is a generated file! #
# All changes made in this file will be lost! #
# # # # # # # # # # # # # # # # # # # # # # # #
""".strip()
# noinspection PyShadowingBuiltins
open = partial(open, encoding="utf-8")
types_to_constructors = {}
types_to_functions = {}
constructors_to_functions = {}
namespaces_to_types = {}
namespaces_to_constructors = {}
namespaces_to_functions = {}
def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False):
if t in core_types:
if t == "long":
return "``int`` ``64-bit``"
elif "int" in t:
size = INT_RE.match(t)
return "``int`` ``{}-bit``".format(size.group(1)) if size else "``int`` ``32-bit``"
elif t == "double":
return "``float`` ``64-bit``"
elif t == "string":
return "``str``"
else:
return "``{}``".format(t.lower())
elif t == "true":
return "``bool``"
elif t == "TLObject" or t == "X":
return "Any object from :obj:`~pyrogram.api.types`"
elif t == "!X":
return "Any method from :obj:`~pyrogram.api.functions`"
elif t.startswith("Vector"):
return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True, is_pyrogram_type)
else:
if is_pyrogram_type:
t = "pyrogram." + t
t = types_to_constructors.get(t, [t])
n = len(t) - 1
t = (("e" if is_list else "E") + "ither " if n else "") + ", ".join(
":obj:`{1} <{0}.{1}>`".format(
"pyrogram.types" if is_pyrogram_type else "pyrogram.api.types",
i.replace("pyrogram.", "")
)
for i in t
)
if n:
t = t.split(", ")
t = ", ".join(t[:-1]) + " or " + t[-1]
return t
class Combinator(NamedTuple):
section: str
qualname: str
namespace: str
name: str
id: str
has_flags: bool
args: List[Tuple[str, str]]
qualtype: str
typespace: str
type: str
def get_references(t: str):
t = constructors_to_functions.get(t)
if t:
n = len(t) - 1
t = ", ".join(
":obj:`{0} <pyrogram.api.functions.{0}>`".format(i)
for i in t
)
if n:
t = t.split(", ")
t = ", ".join(t[:-1]) + " and " + t[-1]
return t
def get_argument_type(arg):
is_flag = FLAGS_RE.match(arg[1])
name, t = arg
if is_flag:
t = t.split("?")[1]
if t in core_types:
if t == "long" or "int" in t:
t = ": int"
elif t == "double":
t = ": float"
elif t == "string":
t = ": str"
else:
t = ": {}".format(t.lower())
elif t == "true":
t = ": bool"
elif t.startswith("Vector"):
t = ": list"
else:
return name + ("=None" if is_flag else "")
return name + t + (" = None" if is_flag else "")
class Combinator:
def __init__(self,
section: str,
namespace: str,
name: str,
id: str,
args: list,
has_flags: bool,
return_type: str,
docs: str):
self.section = section
self.namespace = namespace
self.name = name
self.id = id
self.args = args
self.has_flags = has_flags
self.return_type = return_type
self.docs = docs
def snek(s: str):
# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
def snake(s: str):
# https://stackoverflow.com/q/1175208
s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s)
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s).lower()
def capit(s: str):
def camel(s: str):
return "".join([i[0].upper() + i[1:] for i in s.split("_")])
# noinspection PyShadowingBuiltins, PyShadowingNames
def get_type_hint(type: str) -> str:
is_flag = FLAGS_RE.match(type)
is_core = False
if is_flag:
type = type.split("?")[1]
if type in CORE_TYPES:
is_core = True
if type == "long" or "int" in type:
type = "int"
elif type == "double":
type = "float"
elif type == "string":
type = "str"
elif type in ["Bool", "true"]:
type = "bool"
else: # bytes and object
type = "bytes"
if type in ["Object", "!X"]:
return "TLObject"
if re.match("^vector", type, re.I):
is_core = True
sub_type = type.split("<")[1][:-1]
type = f"List[{get_type_hint(sub_type)}]"
if is_core:
return f"Union[None, {type}] = None" if is_flag else type
else:
ns, name = type.split(".") if "." in type else ("", type)
type = f'"raw.base.' + ".".join([ns, name]).strip(".") + '"'
return f'{type}{" = None" if is_flag else ""}'
def sort_args(args):
"""Put flags at the end"""
args = args.copy()
@@ -163,99 +131,167 @@ def sort_args(args):
for i in flags:
args.remove(i)
try:
args.remove(("flags", "#"))
except ValueError:
pass
return args + flags
def start():
shutil.rmtree("{}/types".format(DESTINATION), ignore_errors=True)
shutil.rmtree("{}/functions".format(DESTINATION), ignore_errors=True)
def remove_whitespaces(source: str) -> str:
"""Remove whitespaces from blank lines"""
lines = source.split("\n")
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
schema = (auth.read() + system.read() + api.read()).splitlines()
for i, _ in enumerate(lines):
if re.match(r"^\s+$", lines[i]):
lines[i] = ""
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
mtproto_template = f.read()
return "\n".join(lines)
with open("{}/template/pyrogram.txt".format(HOME), encoding="utf-8") as f:
pyrogram_template = f.read()
def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False):
if t in CORE_TYPES:
if t == "long":
return "``int`` ``64-bit``"
elif "int" in t:
size = INT_RE.match(t)
return f"``int`` ``{size.group(1)}-bit``" if size else "``int`` ``32-bit``"
elif t == "double":
return "``float`` ``64-bit``"
elif t == "string":
return "``str``"
elif t == "true":
return "``bool``"
else:
return f"``{t.lower()}``"
elif t == "TLObject" or t == "X":
return "Any object from :obj:`~pyrogram.raw.types`"
elif t == "!X":
return "Any method from :obj:`~pyrogram.raw.functions`"
elif t.lower().startswith("vector"):
return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True)
else:
return f":obj:`{t} <pyrogram.raw.base.{t}>`"
def get_references(t: str, kind: str):
if kind == "constructors":
t = constructors_to_functions.get(t)
elif kind == "types":
t = types_to_functions.get(t)
else:
raise ValueError("Invalid kind")
if t:
return "\n ".join(
f"- :obj:`{i} <pyrogram.raw.functions.{i}>`"
for i in t
), len(t)
return None, 0
# noinspection PyShadowingBuiltins
def start(format: bool = False):
shutil.rmtree(DESTINATION_PATH / "types", ignore_errors=True)
shutil.rmtree(DESTINATION_PATH / "functions", ignore_errors=True)
shutil.rmtree(DESTINATION_PATH / "base", ignore_errors=True)
with open(HOME_PATH / "source/auth_key.tl") as f1, \
open(HOME_PATH / "source/sys_msgs.tl") as f2, \
open(HOME_PATH / "source/main_api.tl") as f3:
schema = (f1.read() + f2.read() + f3.read()).splitlines()
with open(HOME_PATH / "template/type.txt") as f1, \
open(HOME_PATH / "template/combinator.txt") as f2:
type_tmpl = f1.read()
combinator_tmpl = f2.read()
with open(NOTICE_PATH, encoding="utf-8") as f:
notice = []
for line in f.readlines():
notice.append("# {}".format(line).strip())
notice.append(f"# {line}".strip())
notice = "\n".join(notice)
section = None
layer = None
namespaces = {"types": set(), "functions": set()}
combinators = []
for line in schema:
# Check for section changer lines
s = SECTION_RE.match(line)
if s:
section = s.group(1)
section_match = SECTION_RE.match(line)
if section_match:
section = section_match.group(1)
continue
# Save the layer version
l = LAYER_RE.match(line)
if l:
layer = l.group(1)
layer_match = LAYER_RE.match(line)
if layer_match:
layer = layer_match.group(1)
continue
combinator = COMBINATOR_RE.match(line)
if combinator:
name, id, return_type, docs = combinator.groups()
namespace, name = name.split(".") if "." in name else ("", name)
args = ARGS_RE.findall(line.split(" //")[0])
combinator_match = COMBINATOR_RE.match(line)
if combinator_match:
# noinspection PyShadowingBuiltins
qualname, id, qualtype = combinator_match.groups()
namespace, name = qualname.split(".") if "." in qualname else ("", qualname)
name = camel(name)
qualname = ".".join([namespace, name]).lstrip(".")
typespace, type = qualtype.split(".") if "." in qualtype else ("", qualtype)
type = camel(type)
qualtype = ".".join([typespace, type]).lstrip(".")
# Pingu!
has_flags = not not FLAGS_RE_3.findall(line)
# Fix file and folder name collision
if name == "updates":
name = "update"
args = ARGS_RE.findall(line)
# Fix arg name being "self" (reserved keyword)
# Fix arg name being "self" (reserved python keyword)
for i, item in enumerate(args):
if item[0] == "self":
args[i] = ("is_self", item[1])
if namespace:
namespaces[section].add(namespace)
combinators.append(
Combinator(
section,
namespace,
capit(name),
"0x{}".format(id.zfill(8)),
args,
has_flags,
".".join(
return_type.split(".")[:-1]
+ [capit(return_type.split(".")[-1])]
),
docs
)
combinator = Combinator(
section=section,
qualname=qualname,
namespace=namespace,
name=name,
id=f"0x{id}",
has_flags=has_flags,
args=args,
qualtype=qualtype,
typespace=typespace,
type=type
)
for c in combinators:
return_type = c.return_type
combinators.append(combinator)
if return_type.startswith("Vector"):
return_type = return_type.split("<")[1][:-1]
for c in combinators:
qualtype = c.qualtype
if qualtype.startswith("Vector"):
qualtype = qualtype.split("<")[1][:-1]
d = types_to_constructors if c.section == "types" else types_to_functions
if return_type not in d:
d[return_type] = []
if qualtype not in d:
d[qualtype] = []
d[return_type].append(".".join(filter(None, [c.namespace, c.name])))
d[qualtype].append(c.qualname)
if c.section == "types":
key = c.namespace
if key not in namespaces_to_types:
namespaces_to_types[key] = []
if c.type not in namespaces_to_types[key]:
namespaces_to_types[key].append(c.type)
for k, v in types_to_constructors.items():
for i in v:
@@ -264,85 +300,100 @@ def start():
except KeyError:
pass
total = len(combinators)
current = 0
for c in combinators: # type: Combinator
print("Compiling APIs... [{}%] {}".format(
str(round(current * 100 / total)).rjust(3),
".".join(filter(None, [c.section, c.namespace, c.name]))
), end=" \r", flush=True)
current += 1
# import json
# print(json.dumps(namespaces_to_types, indent=2))
path = "{}/{}/{}".format(DESTINATION, c.section, c.namespace)
os.makedirs(path, exist_ok=True)
for qualtype in types_to_constructors:
typespace, type = qualtype.split(".") if "." in qualtype else ("", qualtype)
dir_path = DESTINATION_PATH / "base" / typespace
init = "{}/__init__.py".format(path)
module = type
if not os.path.exists(init):
with open(init, "w", encoding="utf-8") as f:
f.write(notice + "\n\n")
if module == "Updates":
module = "UpdatesT"
with open(init, "a", encoding="utf-8") as f:
f.write("from .{} import {}\n".format(snek(c.name), capit(c.name)))
os.makedirs(dir_path, exist_ok=True)
constructors = sorted(types_to_constructors[qualtype])
constr_count = len(constructors)
items = "\n ".join([f"- :obj:`{c} <pyrogram.raw.types.{c}>`" for c in constructors])
docstring = f"This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n"
docstring += f" Constructors:\n .. hlist::\n :columns: 2\n\n {items}"
references, ref_count = get_references(qualtype, "types")
if references:
docstring += f"\n\n See Also:\n This object can be returned by " \
f"{ref_count} method{'s' if ref_count > 1 else ''}:" \
f"\n\n .. hlist::\n :columns: 2\n\n " + references
with open(dir_path / f"{snake(module)}.py", "w") as f:
f.write(
type_tmpl.format(
notice=notice,
warning=WARNING,
docstring=docstring,
name=type,
qualname=qualtype,
types=", ".join([f"raw.types.{c}" for c in constructors])
)
)
for c in combinators:
sorted_args = sort_args(c.args)
arguments = (
", "
+ ("*, " if c.args else "")
+ (", ".join([get_argument_type(i) for i in sorted_args if i != ("flags", "#")]) if c.args else "")
(", *, " if c.args else "") +
(", ".join(
[f"{i[0]}: {get_type_hint(i[1])}"
for i in sorted_args]
) if sorted_args else "")
)
fields = "\n ".join(
["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")]
) if c.args else "pass"
[f"self.{i[0]} = {i[0]} # {i[1]}"
for i in sorted_args]
) if sorted_args else "pass"
docstring = ""
docstring_args = []
docs = c.docs.split("|")[1:] if c.docs else None
for i, arg in enumerate(sorted_args):
if arg == ("flags", "#"):
continue
arg_name, arg_type = arg
is_optional = FLAGS_RE.match(arg_type)
flag_number = is_optional.group(1) if is_optional else -1
arg_type = arg_type.split("?")[-1]
if docs:
docstring_args.append(
"{} ({}{}):\n {}\n".format(
arg_name,
get_docstring_arg_type(arg_type, is_pyrogram_type=True),
", optional" if "Optional" in docs[i] else "",
re.sub("Optional\. ", "", docs[i].split("§")[1].rstrip(".") + ".")
)
)
else:
docstring_args.append(
"{}{}: {}".format(
arg_name,
" (optional)".format(flag_number) if is_optional else "",
get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
)
docstring_args.append(
"{}{}: {}".format(
arg_name,
" (optional)".format(flag_number) if is_optional else "",
get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
)
)
if c.section == "types":
docstring += f"This object is a constructor of the base type :obj:`~pyrogram.raw.base.{c.qualtype}`.\n\n"
else:
docstring += f"Telegram API method.\n\n"
docstring += f" Details:\n - Layer: ``{layer}``\n - ID: ``{c.id}``\n\n"
if docstring_args:
docstring_args = "Parameters:\n " + "\n ".join(docstring_args)
docstring += " Parameters:\n " + "\n ".join(docstring_args)
else:
docstring_args = "No parameters required."
docstring_args = "Attributes:\n ID: ``{}``\n\n ".format(c.id) + docstring_args
docstring_args = "Attributes:\n LAYER: ``{}``\n\n ".format(layer) + docstring_args
docstring += " **No parameters required.**"
if c.section == "functions":
docstring_args += "\n\n Returns:\n " + get_docstring_arg_type(c.return_type)
docstring += "\n\n Returns:\n " + get_docstring_arg_type(c.qualtype)
else:
references = get_references(".".join(filter(None, [c.namespace, c.name])))
references, count = get_references(c.qualname, "constructors")
if references:
docstring_args += "\n\n See Also:\n This object can be returned by " + references + "."
docstring += f"\n\n See Also:\n This object can be returned by " \
f"{count} method{'s' if count > 1 else ''}:" \
f"\n\n .. hlist::\n :columns: 2\n\n " + references
write_types = read_types = "" if c.has_flags else "# No flags\n "
@@ -355,17 +406,16 @@ def start():
for i in c.args:
flag = FLAGS_RE.match(i[1])
if flag:
write_flags.append(
"flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0]))
write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0")
write_flags = "\n ".join([
"flags = 0",
"\n ".join(write_flags),
"b.write(Int(flags))\n "
"data.write(Int(flags))\n "
])
write_types += write_flags
read_types += "flags = Int.read(b)\n "
read_types += "flags = Int.read(data)\n "
continue
@@ -374,126 +424,171 @@ def start():
if flag_type == "true":
read_types += "\n "
read_types += "{} = True if flags & (1 << {}) else False".format(arg_name, index)
elif flag_type in core_types:
read_types += f"{arg_name} = True if flags & (1 << {index}) else False"
elif flag_type in CORE_TYPES:
write_types += "\n "
write_types += "if self.{} is not None:\n ".format(arg_name)
write_types += "b.write({}(self.{}))\n ".format(flag_type.title(), arg_name)
write_types += f"if self.{arg_name} is not None:\n "
write_types += f"data.write({flag_type.title()}(self.{arg_name}))\n "
read_types += "\n "
read_types += "{} = {}.read(b) if flags & (1 << {}) else None".format(
arg_name, flag_type.title(), index
)
read_types += f"{arg_name} = {flag_type.title()}.read(data) if flags & (1 << {index}) else None"
elif "vector" in flag_type.lower():
sub_type = arg_type.split("<")[1][:-1]
write_types += "\n "
write_types += "if self.{} is not None:\n ".format(arg_name)
write_types += "b.write(Vector(self.{}{}))\n ".format(
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
write_types += f"if self.{arg_name} is not None:\n "
write_types += "data.write(Vector(self.{}{}))\n ".format(
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
)
read_types += "\n "
read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n ".format(
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "", index
read_types += "{} = TLObject.read(data{}) if flags & (1 << {}) else []\n ".format(
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index
)
else:
write_types += "\n "
write_types += "if self.{} is not None:\n ".format(arg_name)
write_types += "b.write(self.{}.write())\n ".format(arg_name)
write_types += f"if self.{arg_name} is not None:\n "
write_types += f"data.write(self.{arg_name}.write())\n "
read_types += "\n "
read_types += "{} = TLObject.read(b) if flags & (1 << {}) else None\n ".format(
arg_name, index
)
read_types += f"{arg_name} = TLObject.read(data) if flags & (1 << {index}) else None\n "
else:
if arg_type in core_types:
if arg_type in CORE_TYPES:
write_types += "\n "
write_types += "b.write({}(self.{}))\n ".format(arg_type.title(), arg_name)
write_types += f"data.write({arg_type.title()}(self.{arg_name}))\n "
read_types += "\n "
read_types += "{} = {}.read(b)\n ".format(arg_name, arg_type.title())
read_types += f"{arg_name} = {arg_type.title()}.read(data)\n "
elif "vector" in arg_type.lower():
sub_type = arg_type.split("<")[1][:-1]
write_types += "\n "
write_types += "b.write(Vector(self.{}{}))\n ".format(
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
write_types += "data.write(Vector(self.{}{}))\n ".format(
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
)
read_types += "\n "
read_types += "{} = TLObject.read(b{})\n ".format(
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
read_types += "{} = TLObject.read(data{})\n ".format(
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
)
else:
write_types += "\n "
write_types += "b.write(self.{}.write())\n ".format(arg_name)
write_types += f"data.write(self.{arg_name}.write())\n "
read_types += "\n "
read_types += "{} = TLObject.read(b)\n ".format(arg_name)
read_types += f"{arg_name} = TLObject.read(data)\n "
if c.docs:
description = c.docs.split("|")[0].split("§")[1]
docstring_args = description + "\n\n " + docstring_args
slots = ", ".join([f'"{i[0]}"' for i in sorted_args])
return_arguments = ", ".join([f"{i[0]}={i[0]}" for i in sorted_args])
with open("{}/{}.py".format(path, snek(c.name)), "w", encoding="utf-8") as f:
if c.docs:
f.write(
pyrogram_template.format(
notice=notice,
class_name=capit(c.name),
docstring_args=docstring_args,
object_id=c.id,
arguments=arguments,
fields=fields
)
)
else:
f.write(
mtproto_template.format(
notice=notice,
class_name=capit(c.name),
docstring_args=docstring_args,
object_id=c.id,
arguments=arguments,
fields=fields,
read_types=read_types,
write_types=write_types,
return_arguments=", ".join(
["{0}={0}".format(i[0]) for i in sorted_args if i != ("flags", "#")]
),
slots=", ".join(['"{}"'.format(i[0]) for i in sorted_args if i != ("flags", "#")]),
qualname="{}.{}{}".format(c.section, "{}.".format(c.namespace) if c.namespace else "", c.name)
)
)
compiled_combinator = combinator_tmpl.format(
notice=notice,
warning=WARNING,
name=c.name,
docstring=docstring,
slots=slots,
id=c.id,
qualname=f"pyrogram.raw.{c.section}.{c.qualname}",
arguments=arguments,
fields=fields,
read_types=read_types,
write_types=write_types,
return_arguments=return_arguments
)
with open("{}/all.py".format(DESTINATION), "w", encoding="utf-8") as f:
directory = "types" if c.section == "types" else c.section
dir_path = DESTINATION_PATH / directory / c.namespace
os.makedirs(dir_path, exist_ok=True)
module = c.name
if module == "Updates":
module = "UpdatesT"
with open(dir_path / f"{snake(module)}.py", "w") as f:
f.write(compiled_combinator)
d = namespaces_to_constructors if c.section == "types" else namespaces_to_functions
if c.namespace not in d:
d[c.namespace] = []
d[c.namespace].append(c.name)
for namespace, types in namespaces_to_types.items():
with open(DESTINATION_PATH / "base" / namespace / "__init__.py", "w") as f:
f.write(f"{notice}\n\n")
f.write(f"{WARNING}\n\n")
for t in types:
module = t
if module == "Updates":
module = "UpdatesT"
f.write(f"from .{snake(module)} import {t}\n")
if not namespace:
f.write(f"from . import {', '.join(filter(bool, namespaces_to_types))}")
for namespace, types in namespaces_to_constructors.items():
with open(DESTINATION_PATH / "types" / namespace / "__init__.py", "w") as f:
f.write(f"{notice}\n\n")
f.write(f"{WARNING}\n\n")
for t in types:
module = t
if module == "Updates":
module = "UpdatesT"
f.write(f"from .{snake(module)} import {t}\n")
if not namespace:
f.write(f"from . import {', '.join(filter(bool, namespaces_to_constructors))}\n")
for namespace, types in namespaces_to_functions.items():
with open(DESTINATION_PATH / "functions" / namespace / "__init__.py", "w") as f:
f.write(f"{notice}\n\n")
f.write(f"{WARNING}\n\n")
for t in types:
module = t
if module == "Updates":
module = "UpdatesT"
f.write(f"from .{snake(module)} import {t}\n")
if not namespace:
f.write(f"from . import {', '.join(filter(bool, namespaces_to_functions))}")
with open(DESTINATION_PATH / "all.py", "w", encoding="utf-8") as f:
f.write(notice + "\n\n")
f.write("layer = {}\n\n".format(layer))
f.write(WARNING + "\n\n")
f.write(f"layer = {layer}\n\n")
f.write("objects = {")
for c in combinators:
path = ".".join(filter(None, [c.section, c.namespace, capit(c.name)]))
f.write("\n {}: \"pyrogram.api.{}\",".format(c.id, path))
f.write(f'\n {c.id}: "pyrogram.raw.{c.section}.{c.qualname}",')
f.write("\n 0xbc799737: \"pyrogram.api.core.BoolFalse\",")
f.write("\n 0x997275b5: \"pyrogram.api.core.BoolTrue\",")
f.write("\n 0x1cb5c415: \"pyrogram.api.core.Vector\",")
f.write("\n 0x73f1f8dc: \"pyrogram.api.core.MsgContainer\",")
f.write("\n 0xae500895: \"pyrogram.api.core.FutureSalts\",")
f.write("\n 0x0949d9dc: \"pyrogram.api.core.FutureSalt\",")
f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",")
f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",")
f.write('\n 0xbc799737: "pyrogram.raw.core.BoolFalse",')
f.write('\n 0x997275b5: "pyrogram.raw.core.BoolTrue",')
f.write('\n 0x1cb5c415: "pyrogram.raw.core.Vector",')
f.write('\n 0x73f1f8dc: "pyrogram.raw.core.MsgContainer",')
f.write('\n 0xae500895: "pyrogram.raw.core.FutureSalts",')
f.write('\n 0x0949d9dc: "pyrogram.raw.core.FutureSalt",')
f.write('\n 0x3072cfa1: "pyrogram.raw.core.GzipPacked",')
f.write('\n 0x5bb8e511: "pyrogram.raw.core.Message",')
f.write("\n}\n")
for k, v in namespaces.items():
with open("{}/{}/__init__.py".format(DESTINATION, k), "a", encoding="utf-8") as f:
f.write("from . import {}\n".format(", ".join([i for i in v])) if v else "")
if "__main__" == __name__:
HOME = "."
DESTINATION = "../../pyrogram/api"
NOTICE_PATH = "../../NOTICE"
start()
HOME_PATH = Path(".")
DESTINATION_PATH = Path("../../pyrogram/raw")
NOTICE_PATH = Path("../../NOTICE")
start(format=False)

View File

@@ -87,7 +87,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
userEmpty#200250ba id:int = User;
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
@@ -106,7 +106,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull;
channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@@ -203,7 +203,7 @@ inputReportReasonOther#e1746d0a text:string = ReportReason;
inputReportReasonCopyright#9b89f93a = ReportReason;
inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact;
@@ -796,13 +796,14 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co
inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
phoneCallEmpty#5366c915 id:long = PhoneCall;
phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneCallWaiting#1b8f4ad1 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
phoneCallRequested#87eabb53 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallAccepted#997c454a flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection;
phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
@@ -1376,7 +1377,7 @@ updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto;
photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo;
photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
@@ -1489,4 +1490,4 @@ stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
// LAYER 116
// LAYER 117

View File

@@ -0,0 +1,35 @@
{notice}
from io import BytesIO
from pyrogram.raw.core.primitives import Int, Long, Int128, Int256, Bool, Bytes, String, Double, Vector
from pyrogram.raw.core import TLObject
from pyrogram import raw
from typing import List, Union, Any
{warning}
class {name}(TLObject): # type: ignore
"""{docstring}
"""
__slots__: List[str] = [{slots}]
ID = {id}
QUALNAME = "{qualname}"
def __init__(self{arguments}) -> None:
{fields}
@staticmethod
def read(data: BytesIO, *args: Any) -> "{name}":
{read_types}
return {name}({return_arguments})
def write(self) -> bytes:
data = BytesIO()
data.write(Int(self.ID, False))
{write_types}
return data.getvalue()

View File

@@ -1,30 +0,0 @@
{notice}
from io import BytesIO
from pyrogram.api.core import *
class {class_name}(TLObject):
"""{docstring_args}
"""
__slots__ = [{slots}]
ID = {object_id}
QUALNAME = "{qualname}"
def __init__(self{arguments}):
{fields}
@staticmethod
def read(b: BytesIO, *args) -> "{class_name}":
{read_types}
return {class_name}({return_arguments})
def write(self) -> bytes:
b = BytesIO()
b.write(Int(self.ID, False))
{write_types}
return b.getvalue()

View File

@@ -1,12 +0,0 @@
{notice}
from pyrogram.api.core import Object
class {class_name}(Object):
"""{docstring_args}
"""
ID = {object_id}
def __init__(self{arguments}):
{fields}

View File

@@ -0,0 +1,17 @@
{notice}
{warning}
from typing import Union
from pyrogram import raw
from pyrogram.raw.core import TLObject
{name} = Union[{types}]
# noinspection PyRedeclaration
class {name}(TLObject): # type: ignore
"""{docstring}
"""
QUALNAME = "pyrogram.raw.base.{qualname}"

View File

@@ -25,11 +25,13 @@ HOME = "compiler/docs"
DESTINATION = "docs/source/telegram"
PYROGRAM_API_DEST = "docs/source/api"
FUNCTIONS_PATH = "pyrogram/api/functions"
TYPES_PATH = "pyrogram/api/types"
FUNCTIONS_PATH = "pyrogram/raw/functions"
TYPES_PATH = "pyrogram/raw/types"
BASE_PATH = "pyrogram/raw/base"
FUNCTIONS_BASE = "functions"
TYPES_BASE = "types"
BASE_BASE = "base"
def snek(s: str):
@@ -70,7 +72,7 @@ def generate(source_path, base):
page_template.format(
title=name,
title_markup="=" * len(name),
full_class_path="pyrogram.api.{}".format(
full_class_path="pyrogram.raw.{}".format(
".".join(full_path.split("/")[:-1]) + "." + name
)
)
@@ -92,14 +94,14 @@ def generate(source_path, base):
if k != base:
inner_path = base + "/" + k + "/index" + ".rst"
module = "pyrogram.api.{}.{}".format(base, k)
module = "pyrogram.raw.{}.{}".format(base, k)
else:
for i in sorted(list(all_entities), reverse=True):
if i != base:
entities.insert(0, "{0}/index".format(i))
inner_path = base + "/index" + ".rst"
module = "pyrogram.api.{}".format(base)
module = "pyrogram.raw.{}".format(base)
with open(DESTINATION + "/" + inner_path, "w", encoding="utf-8") as f:
if k == base:
@@ -128,7 +130,6 @@ def pyrogram_api():
utilities="""
Utilities
start
idle
stop
run
restart
@@ -264,6 +265,7 @@ def pyrogram_api():
send_code
resend_code
sign_in
sign_in_bot
sign_up
get_password_hint
check_password
@@ -302,6 +304,15 @@ def pyrogram_api():
f2.write(title + "\n" + "=" * len(title) + "\n\n")
f2.write(".. automethod:: pyrogram.Client.{}()".format(method))
functions = ["idle"]
for func in functions:
with open(root + "/{}.rst".format(func), "w") as f2:
title = "{}()".format(func)
f2.write(title + "\n" + "=" * len(title) + "\n\n")
f2.write(".. autofunction:: pyrogram.{}()".format(func))
f.write(template.format(**fmt_keys))
# Types
@@ -405,7 +416,7 @@ def pyrogram_api():
title = "{}".format(type)
f2.write(title + "\n" + "=" * len(title) + "\n\n")
f2.write(".. autoclass:: pyrogram.{}()".format(type))
f2.write(".. autoclass:: pyrogram.types.{}()".format(type))
f.write(template.format(**fmt_keys))
@@ -506,7 +517,7 @@ def pyrogram_api():
title = "{}()".format(bm)
f2.write(title + "\n" + "=" * len(title) + "\n\n")
f2.write(".. automethod:: pyrogram.{}()".format(bm))
f2.write(".. automethod:: pyrogram.types.{}()".format(bm))
f.write(template.format(**fmt_keys))
@@ -525,12 +536,14 @@ def start():
generate(TYPES_PATH, TYPES_BASE)
generate(FUNCTIONS_PATH, FUNCTIONS_BASE)
generate(BASE_PATH, BASE_BASE)
pyrogram_api()
if "__main__" == __name__:
FUNCTIONS_PATH = "../../pyrogram/api/functions"
TYPES_PATH = "../../pyrogram/api/types"
FUNCTIONS_PATH = "../../pyrogram/raw/functions"
TYPES_PATH = "../../pyrogram/raw/types"
BASE_PATH = "../../pyrogram/raw/base"
HOME = "."
DESTINATION = "../../docs/source/telegram"
PYROGRAM_API_DEST = "../../docs/source/api"

View File

@@ -26,7 +26,7 @@ some of the required arguments.
-----
.. currentmodule:: pyrogram
.. currentmodule:: pyrogram.types
Message
-------

View File

@@ -1,7 +1,8 @@
Available Methods
=================
This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance.
This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance,
except for :meth:`~pyrogram.idle()`, which is a special function that can be found in the main package directly.
.. code-block:: python
:emphasize-lines: 6
@@ -34,6 +35,20 @@ Utilities
{utilities}
.. currentmodule:: pyrogram
.. autosummary::
:nosignatures:
idle
.. toctree::
:hidden:
idle
.. currentmodule:: pyrogram.Client
Messages
--------

View File

@@ -19,7 +19,7 @@ This page is about Pyrogram types. All types listed here are accessible through
-----
.. currentmodule:: pyrogram
.. currentmodule:: pyrogram.types
Users & Chats
-------------

View File

@@ -28,7 +28,7 @@ PASSWORD_HASH_INVALID Two-step verification password is invalid
USERNAME_NOT_OCCUPIED The username is not occupied by anyone
USERNAME_INVALID The username is invalid
MESSAGE_ID_INVALID The message id is invalid
MESSAGE_NOT_MODIFIED The message was not modified
MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content
ENTITY_MENTION_USER_INVALID The mentioned entity is not an user
MESSAGE_TOO_LONG The message text is over 4096 characters
ACCESS_TOKEN_EXPIRED The bot token is invalid
1 id message
28 USERNAME_NOT_OCCUPIED The username is not occupied by anyone
29 USERNAME_INVALID The username is invalid
30 MESSAGE_ID_INVALID The message id is invalid
31 MESSAGE_NOT_MODIFIED The message was not modified The message was not modified because you tried to edit it using the same content
32 ENTITY_MENTION_USER_INVALID The mentioned entity is not an user
33 MESSAGE_TOO_LONG The message text is over 4096 characters
34 ACCESS_TOKEN_EXPIRED The bot token is invalid